// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @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;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { Logo } from "../lib/Logo.sol";
import { ICuratorRewardsDistributor } from "../interfaces/ICuratorRewardsDistributor.sol";
import { IPhiRewards } from "../interfaces/IPhiRewards.sol";
import { ICred } from "../interfaces/ICred.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/// @title CuratorRewardsDistributor
/// @notice Manager of deposits & withdrawals for curator rewards
/// @dev This contract is deployed to same network as the cred contract
contract CuratorRewardsDistributor is Logo, Ownable2Step, ICuratorRewardsDistributor {
/*//////////////////////////////////////////////////////////////
USING
//////////////////////////////////////////////////////////////*/
using SafeTransferLib for address;
/*//////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
IPhiRewards public phiRewardsContract;
ICred public credContract;
uint256 private executeRoyalty = 100;
uint256 private constant RATIO_BASE = 10_000;
uint256 private constant MAX_ROYALTY_RANGE = 1000;
mapping(uint256 credId => uint256 balance) public balanceOf;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address phiRewardsContract_, address credContract_) payable Ownable(_msgSender()) {
if (phiRewardsContract_ == address(0) || credContract_ == address(0)) {
revert InvalidAddressZero();
}
phiRewardsContract = IPhiRewards(phiRewardsContract_);
credContract = ICred(credContract_);
}
/*//////////////////////////////////////////////////////////////
SETTER FUNCTIONS
//////////////////////////////////////////////////////////////*/
function updatePhiRewardsContract(address phiRewardsContract_) external onlyOwner {
if (phiRewardsContract_ == address(0)) {
revert InvalidAddressZero();
}
phiRewardsContract = IPhiRewards(phiRewardsContract_);
emit PhiRewardsContractUpdated(phiRewardsContract_);
}
function updateCredContract(address credContract_) external onlyOwner {
if (credContract_ == address(0)) {
revert InvalidAddressZero();
}
credContract = ICred(credContract_);
emit CredContractUpdated(credContract_);
}
function updateExecuteRoyalty(uint256 newExecuteRoyalty_) external onlyOwner {
if (newExecuteRoyalty_ > MAX_ROYALTY_RANGE) {
revert InvalidRoyalty(newExecuteRoyalty_);
}
executeRoyalty = newExecuteRoyalty_;
emit ExecuteRoyaltyUpdated(newExecuteRoyalty_);
}
/*//////////////////////////////////////////////////////////////
UPDATE FUNCTIONS
//////////////////////////////////////////////////////////////*/
function deposit(uint256 credId, uint256 amount) external payable {
if (!credContract.isExist(credId)) revert InvalidCredId();
if (msg.value != amount) {
revert InvalidValue(msg.value, amount);
}
balanceOf[credId] += amount;
emit Deposit(_msgSender(), credId, amount);
}
function distribute(uint256 credId) external {
if (!credContract.isExist(credId)) revert InvalidCredId();
uint256 totalBalance = balanceOf[credId];
if (totalBalance == 0) {
revert NoBalanceToDistribute();
}
address[] memory distributeAddresses = credContract.getCuratorAddresses(credId, 0, 0);
uint256 totalNum;
for (uint256 i = 0; i < distributeAddresses.length; i++) {
totalNum += credContract.getShareNumber(credId, distributeAddresses[i]);
}
if (totalNum == 0) {
revert NoSharesToDistribute();
}
uint256[] memory amounts = new uint256[](distributeAddresses.length);
bytes4[] memory reasons = new bytes4[](distributeAddresses.length);
uint256 executefee = (totalBalance * executeRoyalty) / RATIO_BASE;
uint256 distributeAmount = totalBalance - executefee;
// actualDistributeAmount is used to avoid rounding errors
// amount[0] = 333 333 333 333 333 333
// amount[1] = 333 333 333 333 333 333
// amount[2] = 333 333 333 333 333 333
uint256 actualDistributeAmount = 0;
for (uint256 i = 0; i < distributeAddresses.length; i++) {
address user = distributeAddresses[i];
uint256 userAmounts = credContract.getShareNumber(credId, user);
uint256 userRewards = (distributeAmount * userAmounts) / totalNum;
if (userRewards > 0) {
amounts[i] = userRewards;
actualDistributeAmount += userRewards;
}
}
balanceOf[credId] -= totalBalance;
_msgSender().safeTransferETH(executefee + distributeAmount - actualDistributeAmount);
//slither-disable-next-line arbitrary-send-eth
phiRewardsContract.depositBatch{ value: actualDistributeAmount }(
distributeAddresses, amounts, reasons, "deposit from curator rewards distributor"
);
emit RewardsDistributed(
credId, _msgSender(), executefee + distributeAmount - actualDistributeAmount, distributeAmount, totalBalance
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
interface ICred {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error InvalidAddressZero();
error InsufficientPayment();
error InsufficientShares();
error AddressNotSigned();
error InvalidCredType();
error InvalidVerificationType();
error UnauthorizedCaller();
error InvalidArrayLength();
error InsufficientBatchPayment();
error Reentrancy();
error InvalidPaginationParameters();
error MaxSupplyReached();
error SignatureExpired();
error InvalidAmount();
error UnauthorizedCurve();
error InvalidMerkleRoot();
error EmptyBatchOperation();
error DuplicateCredId();
error InvalidRoyaltyRange();
error InvalidCredId();
error EmptyArray();
error IndexOutofBounds();
error WrongCredId();
error ShareLockPeriodNotPassed(uint256 currentTimestamp, uint256 unlockTimestamp);
error PriceExceedsLimit();
error PriceBelowLimit();
error InvalidChainId();
error InvalidNonce();
error protocolFeePercentTooHigh();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @notice Emitted phi rewards address is set.
event PhiRewardsAddressSet(address phiRewardsAddress);
/// @notice Emitted when the protocol signer address is set.
event PhiSignerAddressSet(address phiSignerAddress);
/// @notice
event CreatorRoyalty(address indexed creator, uint256 indexed credId, bool isBuy, uint256 Amount);
/// @notice Emitted when a trade occurs.
/// @param curator The address of the curator.
/// @param credId The ID of the cred.
/// @param isBuy Whether the trade is a buy (true) or sell (false).
/// @param amount The amount of shares traded.
/// @param ethAmount The amount of ETH involved in the trade.
/// @param protocolEthAmount The amount of ETH paid as protocol fee.
/// @param supply The new supply of the cred after the trade.
event Trade(
address indexed curator,
uint256 indexed credId,
bool isBuy,
uint256 amount,
uint256 ethAmount,
uint256 protocolEthAmount,
uint256 supply
);
/// @notice Emitted when a new cred is created.
/// @param creator The address of the cred creator.
/// @param credId The ID of the new cred.
/// @param credURL The URL of the cred data.
/// @param credType The type of the cred.
/// @param verificationType The type of verification
/// @param amount The initial amount share buy of the cred.
event CredCreated(
address indexed creator,
uint256 credId,
string credURL,
string credType,
string verificationType,
uint256 amount
);
/// @notice Emitted when the URL of a cred is updated.
/// @param creator The creator of the cred.
/// @param credId The ID of the cred.
/// @param credURL The new URL of the cred data.
event CredUpdated(address indexed creator, uint256 credId, string credURL);
/// @notice Emitted when an address is added to the whitelist.
/// @param sender The address that added the new address to the whitelist.
/// @param whitelistedAddress The address that was added to the whitelist.
event AddedToWhitelist(address indexed sender, address indexed whitelistedAddress);
/// @notice Emitted when an address is removed from the whitelist.
/// @param sender The address that removed the address from the whitelist.
/// @param unwhitelistedAddress The address that was removed from the whitelist.
event RemovedFromWhitelist(address indexed sender, address indexed unwhitelistedAddress);
/// @notice Emitted when the protocol fee percentage is changed.
/// @param changer The address that changed the protocol fee percentage.
/// @param newFee The new protocol fee percentage.
event ProtocolFeePercentChanged(address changer, uint256 newFee);
/// @notice Emitted when the protocol fee destination is changed.
/// @param changer The address that changed the protocol fee destination.
/// @param newDestination The new protocol fee destination.
event ProtocolFeeDestinationChanged(address changer, address newDestination);
/// @notice Emitted when a Merkle tree is set up for a cred.
/// @param sender The address that set up the Merkle tree.
/// @param credId The ID of the cred.
/// @param root The root of the Merkle tree.
event MerkleTreeSetUp(address sender, uint256 credId, bytes32 root);
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
struct CreateCredData {
uint256 expiresIn;
uint256 nonce;
address executor;
uint256 chainId;
address bondingCurve;
string credURL;
string credType;
string verificationType;
bytes32 merkleRoot;
}
struct UpdateCredData {
uint256 expiresIn;
uint256 nonce;
uint256 chainId;
uint256 credId;
string credURL;
}
/// @dev Represents a cred.
struct PhiCred {
address creator;
uint256 currentSupply;
string credURL; // description in arweave
string credType;
string verificationType;
address bondingCurve;
uint16 buyShareRoyalty;
uint16 sellShareRoyalty;
uint40 createdAt;
uint40 updatedAt;
uint256 latestActiveTimestamp;
}
/// @dev Represents a user's curation of a cred.
struct CuratorData {
address curator;
uint256 shareAmount;
}
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice buy a cred.
/// @param credId The ID of the cred.
/// @param amount The amount to buy.
function buyShareCred(uint256 credId, uint256 amount, uint256 maxPrice) external payable;
/// @notice Sell a cred.
/// @param credId The ID of the cred.
/// @param amount The amount to sell.
function sellShareCred(uint256 credId, uint256 amount, uint256 minPrice) external;
// Read functions
/// @notice Gets the creator of a cred.
/// @param credId The ID of the cred.
/// @return The address of the cred creator.
function getCredCreator(uint256 credId) external view returns (address);
/// @notice Gets information about a cred.
/// @param credId The ID of the cred.
/// @return The cred information.
function credInfo(uint256 credId) external view returns (PhiCred memory);
/// @param credId The ID of the cred.
/// @param curator The address to check.
/// @return The number of share the address has for the cred.
function getShareNumber(uint256 credId, address curator) external view returns (uint256);
function createCred(
address creator,
bytes calldata signedData,
bytes calldata signature,
uint16 buyShareRoyalty,
uint16 sellShareRoyalty,
uint256 amount
)
external
payable;
/// @param credId The ID of the cred.
/// @param start The starting index of the range.
/// @param stop The ending index of the range.
/// @return The addresses that have the cred.
function getCuratorAddresses(
uint256 credId,
uint256 start,
uint256 stop
)
external
view
returns (address[] memory);
/// @notice Gets the buy price of a cred for a given amount.
/// @param credId The ID of the cred.
/// @param amount The amount to buy.
/// @return The buy price.
function getCredBuyPrice(uint256 credId, uint256 amount) external view returns (uint256);
/// @notice Gets the buy price of a cred with fee for a given amount.
/// @param credId The ID of the cred.
/// @param amount The amount to buy.
/// @return The buy price with fee.
function getCredBuyPriceWithFee(uint256 credId, uint256 amount) external view returns (uint256);
/// @notice Gets the sell price of a cred for a given amount.
/// @param credId The ID of the cred.
/// @param amount The amount to sell.
/// @return The sell price.
function getCredSellPrice(uint256 credId, uint256 amount) external view returns (uint256);
/// @notice Gets the sell price of a cred with fee for a given amount.
/// @param credId The ID of the cred.
/// @param amount The amount to sell.
/// @return The sell price with fee.
function getCredSellPriceWithFee(uint256 credId, uint256 amount) external view returns (uint256);
/// @notice Gets the total buy price for a batch of creds and amounts.
/// @param credIds The IDs of the creds.
/// @param amounts The amounts corresponding to each cred.
/// @return The total buy price for the batch.
function getBatchBuyPrice(uint256[] calldata credIds, uint256[] calldata amounts) external view returns (uint256);
/// @notice Gets the total sell price for a batch of creds and amounts.
/// @param credIds The IDs of the creds.
/// @param amounts The amounts corresponding to each cred.
/// @return The total sell price for the batch.
function getBatchSellPrice(
uint256[] calldata credIds,
uint256[] calldata amounts
)
external
view
returns (uint256);
/// @notice Checks if a cred exists.
/// @param credId The ID of the cred.
/// @return Whether the cred exists.
function isExist(uint256 credId) external view returns (bool);
/// @param credId The ID of the cred.
/// @param curator The address to check.
/// @return Whether the address has the cred.
function isShareHolder(uint256 credId, address curator) external view returns (bool);
/// @notice Gets the protocol fee percentage.
/// @return The protocol fee percentage.
function protocolFeePercent() external view returns (uint256);
/// @notice Gets the protocol fee destination.
/// @return The protocol fee destination.
function protocolFeeDestination() external view returns (address);
/// @notice Gets the creator royalty percentages.
/// @param credId The ID of the cred.
/// @return The buy and sell royalty.
function getCreatorRoyalty(uint256 credId) external view returns (uint16, uint16);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
interface ICuratorRewardsDistributor {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error InvalidAddressZero();
error NoBalanceToDistribute();
error NoSharesToDistribute();
error InvalidTokenAmounts(uint256 gotAmounts);
error InvalidValue(uint256 gotValue, uint256 expectedValue);
error UnauthorizedCaller(address caller);
error Reentrancy();
error InvalidRoyalty(uint256 royalty);
error InvalidCredId();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Deposit(address sender, uint256 indexed credId, uint256 amount);
event RewardsDistributed(
uint256 indexed credId, address indexed sender, uint256 executefee, uint256 distributeAmount, uint256 total
);
event CredContractUpdated(address newCredContract);
event PhiRewardsContractUpdated(address newPhiRewardsContract);
event ExecuteRoyaltyUpdated(uint256 newRoyalty);
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Gets the balance of the cred.
function balanceOf(uint256 credId) external view returns (uint256);
/// @notice Deposits an amount of ETH into the contract.
/// @param credId The ID of the cred.
/// @param amount The amount of ETH to deposit.
function deposit(uint256 credId, uint256 amount) external payable;
/// @notice Distributes the rewards to the addresses.
/// @param credId The ID of the cred.
function distribute(uint256 credId) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { IRewards } from "./IRewards.sol";
interface IPhiRewards is IRewards {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error ContractNotCreatedByFactory();
error PhiFactoryNotSet();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event PhiFactoryUpdated(address newPhiFactory);
event ArtistRewardUpdated(uint256 artistReward);
event ReferralRewardUpdated(uint256 referralReward);
event VerifierRewardUpdated(uint256 verifierReward);
event CuratorRewardUpdated(uint256 curateReward);
event CuratorRewardsDistributorUpdated(address curatorRewardsDistributor);
event RewardsDeposit(
address minter, address indexed receiver, address indexed referral, address indexed verifier, bytes rewardsData
);
event NotChainSyncDeposit(uint256 artId, address verifier, uint256 curateTotalReward);
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Updates the curator rewards distributor address
/// @param curatorRewardsDistributor_ The new curator rewards distributor address
function updateCuratorRewardsDistributor(address curatorRewardsDistributor_) external;
function setPhiFactory(address phiFactory_) external;
/// @notice Handles rewards and gets the value sent
/// @param artId_ The art ID
/// @param credId_ The credential ID
/// @param quantity_ The quantity
/// @param mintFee_ The minting fee
/// @param addressesData_ The encoded addresses data (minter, receiver, referral, verifier)
/// @param chainSync_ Whether to sync with the chain or not
function handleRewardsAndGetValueSent(
uint256 artId_,
uint256 credId_,
uint256 quantity_,
uint256 mintFee_,
bytes calldata addressesData_,
bool chainSync_
)
external
payable;
/// @notice Computes the minting reward
/// @param quantity_ The quantity
/// @param mintFee_ The minting fee
/// @return The computed minting reward
function computeMintReward(uint256 quantity_, uint256 mintFee_) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
/// @title IRewards
/// @notice The interface for deposits and withdrawals
interface IRewards {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when trying to send to the zero address
error InvalidAddressZero();
/// @notice Thrown when function argument array lengths mismatch
error ArrayLengthMismatch();
/// @notice Thrown when an invalid deposit is made
error InvalidDeposit();
/// @notice Thrown when an invalid signature is provided for a deposit
error InvalidSignature();
/// @notice Thrown when an invalid amount is provided
error InvalidAmount();
/// @notice signature has expired
error DeadlineExpired();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @notice Emitted when a deposit is made
/// @param from The address making the deposit
/// @param to The address receiving the deposit
/// @param reason Optional bytes4 reason for indexing
/// @param amount Amount of the deposit
/// @param comment Optional user comment
event Deposit(address indexed from, address indexed to, bytes4 indexed reason, uint256 amount, string comment);
/// @notice Emitted when a withdrawal is made
/// @param from The address making the withdrawal
/// @param to The address receiving the withdrawal
/// @param amount Amount of the withdrawal
event Withdraw(address indexed from, address indexed to, uint256 amount);
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Deposits ETH for a recipient, with an optional comment
/// @param to Address to deposit to
/// @param reason System reason for deposit (used for indexing)
/// @param comment Optional comment as reason for deposit
function deposit(address to, bytes4 reason, string calldata comment) external payable;
/// @notice Deposits ETH for multiple recipients, with an optional comment
/// @param recipients Recipients to send the amounts to, array aligns with amounts
/// @param amounts Amounts to send to each recipient, array aligns with recipients
/// @param reasons Optional bytes4 hashes for indexing
/// @param comment Optional comment to include with deposit
function depositBatch(
address[] calldata recipients,
uint256[] calldata amounts,
bytes4[] calldata reasons,
string calldata comment
)
external
payable;
/// @notice Withdraws protocol rewards
/// @param to Address to withdraw to
/// @param amount Amount to withdraw
function withdraw(address to, uint256 amount) external;
/// @notice Withdraws rewards on behalf of an address
/// @param to Address to withdraw for
/// @param amount Amount to withdraw (0 for total balance)
function withdrawFor(address to, uint256 amount) external;
/// @notice Executes a withdrawal of protocol rewards via signature
/// @param from Address to withdraw from
/// @param to Address to withdraw to
/// @param amount Amount to withdraw
/// @param deadline Deadline for the signature to be valid
/// @param sig Signature for the withdrawal
function withdrawWithSig(address from, address to, uint256 amount, uint256 deadline, bytes calldata sig) external;
/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Returns the total amount of ETH held in the contract
function totalSupply() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
/*
____ ____
/\___\ /\___\
________/ / /_ \/___/
/\_______\/ /__\___\
/ / / / /
/ / / / / / /
/ / /___/___/___/___/
/ / /
\/___/
*/
interface Logo {
// This is a simple Logo image
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../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.
*
* The initial owner is set to the address provided by the deployer. 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;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling 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 {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_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: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.20;
import {Ownable} from "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is specified at deployment time in the constructor for `Ownable`. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
/// responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for
/// the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
if iszero(
and(
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul(
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
}
{
"compilationTarget": {
"src/reward/CuratorRewardsDistributor.sol": "CuratorRewardsDistributor"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 1000
},
"remappings": [
":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/",
":@prb/test/=node_modules/@prb/test/",
":ds-test/=lib/solady/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":foundry-devops/=lib/foundry-devops/src/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/",
":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/",
":solady/=lib/solady/src/",
":solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/"
]
}
[{"inputs":[{"internalType":"address","name":"phiRewardsContract_","type":"address"},{"internalType":"address","name":"credContract_","type":"address"}],"stateMutability":"payable","type":"constructor"},{"inputs":[],"name":"InvalidAddressZero","type":"error"},{"inputs":[],"name":"InvalidCredId","type":"error"},{"inputs":[{"internalType":"uint256","name":"royalty","type":"uint256"}],"name":"InvalidRoyalty","type":"error"},{"inputs":[{"internalType":"uint256","name":"gotAmounts","type":"uint256"}],"name":"InvalidTokenAmounts","type":"error"},{"inputs":[{"internalType":"uint256","name":"gotValue","type":"uint256"},{"internalType":"uint256","name":"expectedValue","type":"uint256"}],"name":"InvalidValue","type":"error"},{"inputs":[],"name":"NoBalanceToDistribute","type":"error"},{"inputs":[],"name":"NoSharesToDistribute","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"Reentrancy","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"UnauthorizedCaller","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newCredContract","type":"address"}],"name":"CredContractUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"uint256","name":"credId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newRoyalty","type":"uint256"}],"name":"ExecuteRoyaltyUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newPhiRewardsContract","type":"address"}],"name":"PhiRewardsContractUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"credId","type":"uint256"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"executefee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"distributeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"total","type":"uint256"}],"name":"RewardsDistributed","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"credId","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"credContract","outputs":[{"internalType":"contract ICred","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"credId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"credId","type":"uint256"}],"name":"distribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"phiRewardsContract","outputs":[{"internalType":"contract IPhiRewards","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"credContract_","type":"address"}],"name":"updateCredContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newExecuteRoyalty_","type":"uint256"}],"name":"updateExecuteRoyalty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"phiRewardsContract_","type":"address"}],"name":"updatePhiRewardsContract","outputs":[],"stateMutability":"nonpayable","type":"function"}]