// 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.4;
/// @notice Contract for EIP-712 typed structured data hashing and signing.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol)
/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol)
///
/// @dev Note, this implementation:
/// - Uses `address(this)` for the `verifyingContract` field.
/// - Does NOT use the optional EIP-712 salt.
/// - Does NOT use any EIP-712 extensions.
/// This is for simplicity and to save gas.
/// If you need to customize, please fork / modify accordingly.
abstract contract EIP712 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS AND IMMUTABLES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 internal constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
uint256 private immutable _cachedThis;
uint256 private immutable _cachedChainId;
bytes32 private immutable _cachedNameHash;
bytes32 private immutable _cachedVersionHash;
bytes32 private immutable _cachedDomainSeparator;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTRUCTOR */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Cache the hashes for cheaper runtime gas costs.
/// In the case of upgradeable contracts (i.e. proxies),
/// or if the chain id changes due to a hard fork,
/// the domain separator will be seamlessly calculated on-the-fly.
constructor() {
_cachedThis = uint256(uint160(address(this)));
_cachedChainId = block.chainid;
string memory name;
string memory version;
if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion();
bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name));
bytes32 versionHash =
_domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version));
_cachedNameHash = nameHash;
_cachedVersionHash = versionHash;
bytes32 separator;
if (!_domainNameAndVersionMayChange()) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
separator := keccak256(m, 0xa0)
}
}
_cachedDomainSeparator = separator;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* FUNCTIONS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Please override this function to return the domain name and version.
/// ```
/// function _domainNameAndVersion()
/// internal
/// pure
/// virtual
/// returns (string memory name, string memory version)
/// {
/// name = "Solady";
/// version = "1";
/// }
/// ```
///
/// Note: If the returned result may change after the contract has been deployed,
/// you must override `_domainNameAndVersionMayChange()` to return true.
function _domainNameAndVersion()
internal
view
virtual
returns (string memory name, string memory version);
/// @dev Returns if `_domainNameAndVersion()` may change
/// after the contract has been deployed (i.e. after the constructor).
/// Default: false.
function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the EIP-712 domain separator.
function _domainSeparator() internal view virtual returns (bytes32 separator) {
if (_domainNameAndVersionMayChange()) {
separator = _buildDomainSeparator();
} else {
separator = _cachedDomainSeparator;
if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator();
}
}
/// @dev Returns the hash of the fully encoded EIP-712 message for this domain,
/// given `structHash`, as defined in
/// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct.
///
/// The hash can be used together with {ECDSA-recover} to obtain the signer of a message:
/// ```
/// bytes32 digest = _hashTypedData(keccak256(abi.encode(
/// keccak256("Mail(address to,string contents)"),
/// mailTo,
/// keccak256(bytes(mailContents))
/// )));
/// address signer = ECDSA.recover(digest, signature);
/// ```
function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) {
// We will use `digest` to store the domain separator to save a bit of gas.
if (_domainNameAndVersionMayChange()) {
digest = _buildDomainSeparator();
} else {
digest = _cachedDomainSeparator;
if (_cachedDomainSeparatorInvalidated()) digest = _buildDomainSeparator();
}
/// @solidity memory-safe-assembly
assembly {
// Compute the digest.
mstore(0x00, 0x1901000000000000) // Store "\x19\x01".
mstore(0x1a, digest) // Store the domain separator.
mstore(0x3a, structHash) // Store the struct hash.
digest := keccak256(0x18, 0x42)
// Restore the part of the free memory slot that was overwritten.
mstore(0x3a, 0)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-5267 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev See: https://eips.ethereum.org/EIPS/eip-5267
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
fields = hex"0f"; // `0b01111`.
(name, version) = _domainNameAndVersion();
chainId = block.chainid;
verifyingContract = address(this);
salt = salt; // `bytes32(0)`.
extensions = extensions; // `new uint256[](0)`.
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the EIP-712 domain separator.
function _buildDomainSeparator() private view returns (bytes32 separator) {
// We will use `separator` to store the name hash to save a bit of gas.
bytes32 versionHash;
if (_domainNameAndVersionMayChange()) {
(string memory name, string memory version) = _domainNameAndVersion();
separator = keccak256(bytes(name));
versionHash = keccak256(bytes(version));
} else {
separator = _cachedNameHash;
versionHash = _cachedVersionHash;
}
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), separator) // Name hash.
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
separator := keccak256(m, 0xa0)
}
}
/// @dev Returns if the cached domain separator has been invalidated.
function _cachedDomainSeparatorInvalidated() private view returns (bool result) {
uint256 cachedChainId = _cachedChainId;
uint256 cachedThis = _cachedThis;
/// @solidity memory-safe-assembly
assembly {
result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis)))
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { IERC2981 } from "@openzeppelin/contracts/interfaces/IERC2981.sol";
interface ICreatorRoyaltiesControl is IERC2981 {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error AlreadyInitialized();
error NotInitialized();
error RoyaltyTooHigh();
error InvalidRoyaltyRecipient();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @notice Event emitted when royalties are updated
event UpdatedRoyalties(uint256 indexed tokenId, address indexed user, RoyaltyConfiguration configuration);
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
/// @notice The RoyaltyConfiguration struct is used to store the royalty configuration for a given token.
/// @param royaltyMintSchedule Every nth token will go to the royalty recipient.
/// @param royaltyBPS The royalty amount in basis points for secondary sales.
/// @param royaltyRecipient The address that will receive the royalty payments.
struct RoyaltyConfiguration {
uint32 royaltyBPS;
address royaltyRecipient;
}
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice External data getter to get royalties for a token
/// @param tokenId tokenId to get royalties configuration for
function getRoyalties(uint256 tokenId) external view returns (RoyaltyConfiguration memory);
}
// 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
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @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: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC2981.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";
/**
* @dev Interface for the NFT Royalty Standard.
*
* A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
* support for royalty payments across all NFT marketplaces and ecosystem participants.
*/
interface IERC2981 is IERC165 {
/**
* @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
* exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
*/
function royaltyInfo(
uint256 tokenId,
uint256 salePrice
) external view returns (address receiver, uint256 royaltyAmount);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { ICreatorRoyaltiesControl } from "./ICreatorRoyaltiesControl.sol";
interface IPhiFactory {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error AddressNotSigned();
error InvalidAddressZero();
error InvalidMintFee();
error OverMaxAllowedToMint();
error ArtCreatFeeTooHigh();
error ProtocolFeeTooHigh();
error Reentrancy();
error ClaimFailed();
error CreateFailed();
error TxOriginMismatch();
error SignatureExpired();
error InvalidMerkleProof();
error InvalidVerificationType();
error InvalidArtCreator();
error EndTimeInPast();
error EndTimeLessThanOrEqualToStartTime();
error ArtNotStarted();
error ArtEnded();
error ArtAlreadyCreated();
error ExceedMaxSupply();
error InvalidQuantity();
error InvalidEthValue();
error ArrayLengthMismatch();
error ArtNotCreated();
error InvalidChainId();
error InvalidExecutor();
error InvalidNonce();
error InvalidMaxSupply();
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
struct CreateConfig {
address artist;
address receiver;
uint256 endTime;
uint256 startTime;
uint256 maxSupply;
uint256 mintFee;
bool soulBounded;
}
struct UpdateConfig {
address receiver;
uint256 endTime;
uint256 startTime;
uint256 maxSupply;
uint256 mintFee;
uint32 royaltyBPS;
address royaltyRecipient;
}
struct SigClaimData {
uint256 expiresIn; // Timestamp when the signature expires
address minter; // Address of the account minting the NFT
address ref; // Referrer's address for potential referral rewards
address verifier; // Address of the entity verifying the claim
uint256 artId; // Unique identifier for the art piece
uint256 chainId; // ID of the blockchain where the transaction occurs
bytes32 data; // Additional data (usage depends on specific requirements)
string snapshotImage; // URI of the snapshot image
}
struct MerkleClaimData {
uint256 expiresIn;
address minter;
address ref;
uint256 artId;
uint256 chainId;
string snapshotImage;
}
struct ERC1155Data {
address artist;
address receiver;
uint256 artChainId;
uint256 maxSupply;
uint256 endTime;
uint256 startTime;
bytes credData;
uint256 artId;
string uri;
uint256 mintFee;
bool soulBounded;
}
struct CreateSignatureData {
uint256 expiresIn;
uint256 signedChainId;
uint256 nonce;
address executor;
string uri;
bytes credData;
}
// bytes32 credData;
// =>uint256 credId,address credCreator,string memory verificationType,uint256 credChainId,bytes32 merkleRoot
struct UpdateSignatureData {
uint256 expiresIn;
uint256 signedChainId;
uint256 nonce;
uint256 artId;
string url;
}
struct PhiArt {
uint256 credId;
address credCreator;
uint256 credChainId;
string verificationType;
string uri;
address artAddress;
address artist;
address receiver;
uint256 maxSupply;
uint256 mintFee;
uint256 startTime;
uint256 endTime;
uint256 numberMinted;
bool soulBounded;
}
struct ArtData {
uint256 credId;
address credCreator;
uint256 credChainId;
string verificationType;
string uri;
address artAddress;
uint256 tokenId;
address artist;
address receiver;
ICreatorRoyaltiesControl.RoyaltyConfiguration royalties;
uint256 maxSupply;
uint256 mintFee;
uint256 startTime;
uint256 endTime;
uint256 numberMinted;
bool soulBounded;
}
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event ArtClaimedData(
uint256 indexed artId,
string verificationType,
address indexed recipient,
address ref,
address verifier,
address artAddress,
uint256 quantity,
string snapshotImage
);
event NewArtCreated(
address indexed artist,
uint256 indexed credId,
uint256 indexed credChainId,
uint256 artId,
string url,
address artAddress,
uint256 tokenId
);
event ArtContractCreated(
address artist, address contractAddress, uint256 indexed credId, uint256 indexed credChainId
);
event ProtocolFeeSet(uint256 fee);
event ArtCreatFeeSet(uint256 fee);
event ArtUpdated(
uint256 indexed artId,
string url,
address receiver,
uint256 maxSupply,
uint256 mintFee,
uint256 startTime,
uint256 endTime
);
event PhiSignerAddressSet(address phiSignerAddress);
event PhiRewardsAddressSet(address phiRewardsAddress);
event ERC1155ArtAddressSet(address erc1155ArtAddress);
event ProtocolFeeDestinationSet(address protocolFeeDestination);
/*//////////////////////////////////////////////////////////////
VIEWS
//////////////////////////////////////////////////////////////*/
function mintProtocolFee() external view returns (uint256);
function protocolFeeDestination() external view returns (address);
function phiRewardsAddress() external view returns (address);
function artCreateFee() external view returns (uint256);
function getNumberMinted(uint256 artId) external view returns (uint256);
function artData(uint256 artId) external view returns (ArtData memory);
function getArtMintFee(uint256 artId, uint256 quantity) external view returns (uint256);
function getTotalMintFee(uint256[] memory artId, uint256[] memory quantity) external view returns (uint256);
function isCredMinted(uint256 credChainId, uint256 credId, address minter) external view returns (bool);
function isArtMinted(uint256 artId, address minter) external view returns (bool);
function contractURI(address nft) external view returns (string memory);
function getArtAddress(uint256 artId) external view returns (address);
function getTokenURI(uint256 artId) external view returns (string memory);
function createdContracts(address art) external view returns (bool);
/*//////////////////////////////////////////////////////////////
OPERATIONS
//////////////////////////////////////////////////////////////*/
function signatureClaim(bytes calldata signature, bytes calldata encodeData, uint256 quantity_) external payable;
function merkleClaim(
bytes32[] calldata proof_,
bytes calldata encodeData_,
bytes32 leafPart_,
bytes calldata signature_,
uint256 quantity_
)
external
payable;
function claim(bytes calldata encodeData) external payable;
function batchClaim(bytes[] calldata encodeData, uint256[] calldata ethValue) external payable;
function setPhiSignerAddress(address phiSignerAddress) external;
function setErc1155ArtAddress(address erc1155ArtAddress) external;
function setProtocolFeeDestination(address protocolFeeDestination) external;
function setProtocolFee(uint256 protocolFee) external;
function setArtCreatFee(uint256 artFee) 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.25;
import { Logo } from "../lib/Logo.sol";
import { IPhiRewards } from "../interfaces/IPhiRewards.sol";
import { IPhiFactory } from "../interfaces/IPhiFactory.sol";
import { RewardControl } from "../abstract/RewardControl.sol";
import { ICuratorRewardsDistributor } from "../interfaces/ICuratorRewardsDistributor.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 PhiRewards
/// @notice Manager of deposits & withdrawals for art rewards
contract PhiRewards is Logo, RewardControl, Ownable2Step, IPhiRewards {
/*//////////////////////////////////////////////////////////////
USING
//////////////////////////////////////////////////////////////*/
using SafeTransferLib for address;
/*//////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
ICuratorRewardsDistributor public curatorRewardsDistributor;
IPhiFactory public phiFactory;
uint256 public artistReward = 0.0001 ether;
uint256 public referralReward = 0.00005 ether;
uint256 public verifierReward = 0.0001 ether;
uint256 public curatorReward = 0.00005 ether;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address ownerAddress_) payable Ownable(ownerAddress_) { }
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
modifier onlyFactoryCreateArt(address address_) {
if (address(phiFactory) == address(0)) revert PhiFactoryNotSet();
if (!phiFactory.createdContracts(address_)) {
revert ContractNotCreatedByFactory();
}
_;
}
/*//////////////////////////////////////////////////////////////
SETTER FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Update the PhiFactory address
/// @param newPhiFactory_ The address of the new PhiFactory contract
function setPhiFactory(address newPhiFactory_) external onlyOwner {
if (newPhiFactory_ == address(0)) revert InvalidAddressZero();
phiFactory = IPhiFactory(newPhiFactory_);
emit PhiFactoryUpdated(newPhiFactory_);
}
/// @notice Update artist reward amount
/// @param newArtistReward_ New artist reward amount
function updateArtistReward(uint256 newArtistReward_) external onlyOwner {
artistReward = newArtistReward_;
emit ArtistRewardUpdated(newArtistReward_);
}
/// @notice Update referral reward amount
/// @param newReferralReward_ New referral reward amount
function updateReferralReward(uint256 newReferralReward_) external onlyOwner {
referralReward = newReferralReward_;
emit ReferralRewardUpdated(newReferralReward_);
}
/// @notice Update verify reward amount
/// @param newVerifyReward_ New verify reward amount
function updateVerifierReward(uint256 newVerifyReward_) external onlyOwner {
verifierReward = newVerifyReward_;
emit VerifierRewardUpdated(newVerifyReward_);
}
/// @notice Update curate reward amount
/// @param newCurateReward_ New curate reward amount
function updateCurateReward(uint256 newCurateReward_) external onlyOwner {
curatorReward = newCurateReward_;
emit CuratorRewardUpdated(newCurateReward_);
}
/// @notice Update curator rewards distributor
/// @dev This method is only used credential contract is deployed on a same network,
/// if not, it should be set to address(0)
function updateCuratorRewardsDistributor(address curatorRewardsDistributor_) external onlyOwner {
curatorRewardsDistributor = ICuratorRewardsDistributor(curatorRewardsDistributor_);
emit CuratorRewardsDistributorUpdated(curatorRewardsDistributor_);
}
/*//////////////////////////////////////////////////////////////
EXTERNAL UPDATE
//////////////////////////////////////////////////////////////*/
/// @notice deposit protocol rewards
/// @param credId_ Cred ID
function depositRewards(
uint256 artId_,
uint256 credId_,
bytes calldata addressesData_,
uint256 artistTotalReward_,
uint256 referralTotalReward_,
uint256 verifierTotalReward_,
uint256 curateTotalReward_,
bool chainSync_
)
internal
{
(address minter_, address receiver_, address referral_, address verifier_) =
abi.decode(addressesData_, (address, address, address, address));
if (receiver_ == address(0) || minter_ == address(0) || verifier_ == address(0)) {
revert InvalidAddressZero();
}
if (referral_ == minter_ || referral_ == address(0)) {
artistTotalReward_ += referralTotalReward_;
referralTotalReward_ = 0;
} else if (referral_ != address(0)) {
balanceOf[referral_] += referralTotalReward_;
}
balanceOf[verifier_] += verifierTotalReward_;
balanceOf[receiver_] += artistTotalReward_;
bytes memory rewardsData;
if (chainSync_ && address(curatorRewardsDistributor) != address(0)) {
//slither-disable-next-line arbitrary-send-eth
curatorRewardsDistributor.deposit{ value: curateTotalReward_ }(credId_, curateTotalReward_);
rewardsData =
abi.encode(artId_, artistTotalReward_, referralTotalReward_, verifierTotalReward_, curateTotalReward_);
} else {
balanceOf[verifier_] += curateTotalReward_;
rewardsData = abi.encode(
artId_, artistTotalReward_, referralTotalReward_, verifierTotalReward_ + curateTotalReward_, 0
);
emit NotChainSyncDeposit(artId_, verifier_, curateTotalReward_);
}
emit RewardsDeposit(minter_, receiver_, referral_, verifier_, rewardsData);
}
function handleRewardsAndGetValueSent(
uint256 artId_,
uint256 credId_,
uint256 quantity_,
uint256 mintFee_,
bytes calldata addressesData_,
bool chainSync_
)
external
payable
onlyFactoryCreateArt(_msgSender())
{
if (computeMintReward(quantity_, mintFee_) != msg.value) {
revert InvalidDeposit();
}
depositRewards(
artId_,
credId_,
addressesData_,
quantity_ * (mintFee_ + artistReward),
quantity_ * referralReward,
quantity_ * verifierReward,
quantity_ * curatorReward,
chainSync_
);
}
/*//////////////////////////////////////////////////////////////
EXTERNAL VIEW
//////////////////////////////////////////////////////////////*/
function computeMintReward(uint256 quantity_, uint256 mintFee_) public view returns (uint256) {
return quantity_ * (artistReward + mintFee_ + referralReward + verifierReward + curatorReward);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { EIP712 } from "solady/utils/EIP712.sol";
import { IRewards } from "../interfaces/IRewards.sol";
import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
/// @title RewardControl
/// @notice Abstract contract for managing rewards
abstract contract RewardControl is IRewards, EIP712 {
/*//////////////////////////////////////////////////////////////
USING
//////////////////////////////////////////////////////////////*/
using SafeTransferLib for address;
/*//////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
uint256 private constant FULL_BALANCE = 0;
/// @dev The EIP-712 typehash for gasless withdraws
bytes32 public constant WITHDRAW_TYPEHASH =
keccak256("Withdraw(address from,address to,uint256 amount,uint256 nonce,uint256 expiresIn,uint256 chainId)");
/// @dev An account's nonce for gasless withdraws
mapping(address account => uint256 nonce) public nonces;
/// @dev An account's balance
mapping(address account => uint256 balance) public balanceOf;
/*//////////////////////////////////////////////////////////////
UPDATE FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Deposits ETH for a recipient, with an optional comment
/// @param to Address to deposit to
/// @param reason System reason for deposit
/// @param comment Optional comment as reason for deposit
function deposit(address to, bytes4 reason, string calldata comment) external payable {
if (to == address(0)) revert InvalidAddressZero();
unchecked {
balanceOf[to] += msg.value;
}
emit Deposit(msg.sender, to, reason, msg.value, comment);
}
/// @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
{
uint256 numRecipients = recipients.length;
if (numRecipients != amounts.length || numRecipients != reasons.length) {
revert ArrayLengthMismatch();
}
uint256 expectedTotalValue;
for (uint256 i = 0; i < numRecipients; i++) {
address recipient = recipients[i];
uint256 amount = amounts[i];
if (recipient == address(0)) {
revert InvalidAddressZero();
}
balanceOf[recipient] += amount;
expectedTotalValue += amount;
emit Deposit(msg.sender, recipient, reasons[i], amount, comment);
}
if (msg.value != expectedTotalValue) {
revert InvalidDeposit();
}
}
function withdraw(address to, uint256 amount) external {
_withdraw(msg.sender, to, amount);
}
function withdrawFor(address from, uint256 amount) external {
_withdraw(from, from, amount);
}
function withdrawWithSig(
address from,
address to,
uint256 amount,
uint256 expiresIn,
bytes calldata sig
)
external
{
if (block.timestamp > expiresIn) revert DeadlineExpired();
if (!_verifySignature(from, to, amount, nonces[from], expiresIn, block.chainid, sig)) revert InvalidSignature();
unchecked {
++nonces[from];
}
_withdraw(from, to, amount);
}
/*//////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Returns the total amount of ETH held in the contract
function totalSupply() external view returns (uint256) {
return address(this).balance;
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
function _withdraw(address from, address to, uint256 amount) internal {
if (to == address(0)) revert InvalidAddressZero();
uint256 balance = balanceOf[from];
if (amount == FULL_BALANCE) {
amount = balance;
}
if (amount > balance) revert InvalidAmount();
unchecked {
balanceOf[from] = balance - amount;
}
emit Withdraw(from, to, amount);
to.safeTransferETH(amount);
}
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
return ("PHI Rewards", "1");
}
/// @dev Verifies EIP-712 `Mint` signature
function _verifySignature(
address from,
address to,
uint256 amount,
uint256 nonce,
uint256 expiresIn,
uint256 chainId,
bytes calldata sig
)
internal
view
returns (bool)
{
bytes32 structHash = keccak256(abi.encode(WITHDRAW_TYPEHASH, from, to, amount, nonce, expiresIn, chainId));
bytes32 digest = _hashTypedData(structHash);
// address signer, bytes32 hash, bytes calldata signature
return SignatureCheckerLib.isValidSignatureNowCalldata(from, digest, sig);
}
/// @dev EIP-712 helper
function hashTypedData(bytes32 structHash) public view returns (bytes32) {
return _hashTypedData(structHash);
}
}
// 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)
)
)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Signature verification helper that supports both ECDSA signatures from EOAs
/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol)
///
/// @dev Note:
/// - The signature checking functions use the ecrecover precompile (0x1).
/// - The `bytes memory signature` variants use the identity precompile (0x4)
/// to copy memory internally.
/// - Unlike ECDSA signatures, contract signatures are revocable.
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both
/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures.
/// See: https://eips.ethereum.org/EIPS/eip-2098
/// This is for calldata efficiency on smart accounts prevalent on L2s.
///
/// WARNING! Do NOT use signatures as unique identifiers:
/// - Use a nonce in the digest to prevent replay attacks on the same contract.
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts.
/// EIP-712 also enables readable signing of typed data for better user safety.
/// This implementation does NOT check if a signature is non-malleable.
library SignatureCheckerLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIGNATURE CHECKING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x40, mload(add(signature, 0x20))) // `r`.
if eq(mload(signature), 64) {
let vs := mload(add(signature, 0x40))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(mload(signature), 65) {
mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
mstore(0x60, mload(add(signature, 0x40))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether `signature` is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
if eq(signature.length, 64) {
let vs := calldataload(add(signature.offset, 0x20))
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, calldataload(signature.offset)) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
if eq(signature.length, 65) {
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, add(shr(255, vs), 27)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, shr(1, shl(1, vs))) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), mload(0x60)) // `s`.
mstore8(add(m, 0xa4), mload(0x20)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`.
/// If `signer` is a smart contract, the signature is validated with ERC1271.
/// Otherwise, the signature is validated with `ECDSA.recover`.
function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for { signer := shr(96, shl(96, signer)) } signer {} {
let m := mload(0x40)
mstore(0x00, hash)
mstore(0x20, and(v, 0xff)) // `v`.
mstore(0x40, r) // `r`.
mstore(0x60, s) // `s`.
let t :=
staticcall(
gas(), // Amount of gas left for the transaction.
1, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x01, // Start of output.
0x20 // Size of output.
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
isValid := 1
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
mstore(0x60, 0) // Restore the zero slot.
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1271 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
// Copy the `signature` over.
let n := add(0x20, mload(signature))
pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(returndatasize(), 0x44), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
function isValidERC1271SignatureNowCalldata(
address signer,
bytes32 hash,
bytes calldata signature
) internal view returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), signature.length)
// Copy the `signature` over.
calldatacopy(add(m, 0x64), signature.offset, signature.length)
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
add(signature.length, 0x64), // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`r`, `vs`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`.
mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash`
/// for an ERC1271 `signer` contract.
function isValidERC1271SignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (bool isValid)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
let f := shl(224, 0x1626ba7e)
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
let d := add(m, 0x24)
mstore(d, 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Length of the signature.
mstore(add(m, 0x64), r) // `r`.
mstore(add(m, 0x84), s) // `s`.
mstore8(add(m, 0xa4), v) // `v`.
// forgefmt: disable-next-item
isValid := and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(d), f),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
d, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an Ethereum Signed Message, created from a `hash`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, hash) // Store into scratch space for keccak256.
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
}
}
/// @dev Returns an Ethereum Signed Message, created from `s`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
/// Note: Supports lengths of `s` up to 999999 bytes.
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let sLength := mload(s)
let o := 0x20
mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.
mstore(0x00, 0x00)
// Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.
for { let temp := sLength } 1 {} {
o := sub(o, 1)
mstore8(o, add(48, mod(temp, 10)))
temp := div(temp, 10)
if iszero(temp) { break }
}
let n := sub(0x3a, o) // Header length: `26 + 32 - o`.
// Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
result := keccak256(add(s, sub(0x20, n)), add(n, sLength))
mstore(s, sLength) // Restore the length.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EMPTY CALLDATA HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an empty calldata bytes.
function emptySignature() internal pure returns (bytes calldata signature) {
/// @solidity memory-safe-assembly
assembly {
signature.length := 0
}
}
}
{
"compilationTarget": {
"src/reward/PhiRewards.sol": "PhiRewards"
},
"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":"ownerAddress_","type":"address"}],"stateMutability":"payable","type":"constructor"},{"inputs":[],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"ContractNotCreatedByFactory","type":"error"},{"inputs":[],"name":"DeadlineExpired","type":"error"},{"inputs":[],"name":"InvalidAddressZero","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidDeposit","type":"error"},{"inputs":[],"name":"InvalidSignature","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":"PhiFactoryNotSet","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"artistReward","type":"uint256"}],"name":"ArtistRewardUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"curateReward","type":"uint256"}],"name":"CuratorRewardUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"curatorRewardsDistributor","type":"address"}],"name":"CuratorRewardsDistributorUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"bytes4","name":"reason","type":"bytes4"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"string","name":"comment","type":"string"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"artId","type":"uint256"},{"indexed":false,"internalType":"address","name":"verifier","type":"address"},{"indexed":false,"internalType":"uint256","name":"curateTotalReward","type":"uint256"}],"name":"NotChainSyncDeposit","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":"newPhiFactory","type":"address"}],"name":"PhiFactoryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"referralReward","type":"uint256"}],"name":"ReferralRewardUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"minter","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"referral","type":"address"},{"indexed":true,"internalType":"address","name":"verifier","type":"address"},{"indexed":false,"internalType":"bytes","name":"rewardsData","type":"bytes"}],"name":"RewardsDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"verifierReward","type":"uint256"}],"name":"VerifierRewardUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"WITHDRAW_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"artistReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity_","type":"uint256"},{"internalType":"uint256","name":"mintFee_","type":"uint256"}],"name":"computeMintReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"curatorReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"curatorRewardsDistributor","outputs":[{"internalType":"contract ICuratorRewardsDistributor","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes4","name":"reason","type":"bytes4"},{"internalType":"string","name":"comment","type":"string"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes4[]","name":"reasons","type":"bytes4[]"},{"internalType":"string","name":"comment","type":"string"}],"name":"depositBatch","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"artId_","type":"uint256"},{"internalType":"uint256","name":"credId_","type":"uint256"},{"internalType":"uint256","name":"quantity_","type":"uint256"},{"internalType":"uint256","name":"mintFee_","type":"uint256"},{"internalType":"bytes","name":"addressesData_","type":"bytes"},{"internalType":"bool","name":"chainSync_","type":"bool"}],"name":"handleRewardsAndGetValueSent","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"structHash","type":"bytes32"}],"name":"hashTypedData","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","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":"phiFactory","outputs":[{"internalType":"contract IPhiFactory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"referralReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newPhiFactory_","type":"address"}],"name":"setPhiFactory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newArtistReward_","type":"uint256"}],"name":"updateArtistReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newCurateReward_","type":"uint256"}],"name":"updateCurateReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"curatorRewardsDistributor_","type":"address"}],"name":"updateCuratorRewardsDistributor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newReferralReward_","type":"uint256"}],"name":"updateReferralReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newVerifyReward_","type":"uint256"}],"name":"updateVerifierReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"verifierReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"expiresIn","type":"uint256"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"withdrawWithSig","outputs":[],"stateMutability":"nonpayable","type":"function"}]