Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/access/IAccessControl.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC-165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call. This account bears the admin role (for the granted role).
* Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}
"
},
"@openzeppelin/contracts/interfaces/IERC1271.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1271.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-1271 standard signature validation method for
* contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
*/
interface IERC1271 {
/**
* @dev Should return whether the signature provided is valid for the provided data
* @param hash Hash of the data to be signed
* @param signature Signature byte array associated with _data
*/
function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
}
"
},
"@openzeppelin/contracts/token/ERC721/IERC721.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC-721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC-721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
"
},
"@openzeppelin/contracts/utils/introspection/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* 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[ERC 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);
}
"
},
"contracts/protocol/bases/mixins/Access.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
import { FermionTypes } from "../../domain/Types.sol";
import { FermionStorage } from "../../libs/Storage.sol";
import { PauseErrors, FermionGeneralErrors } from "../../domain/Errors.sol";
import { Context } from "./Context.sol";
import { ReentrancyGuard } from "./ReentrancyGuard.sol";
/**
* @title Access control
*
* @notice Provides access to the protocol
*/
contract Access is Context, ReentrancyGuard {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
struct AccessControlStorage {
mapping(bytes32 role => RoleData) _roles;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlStorageLocation =
0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;
function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
assembly {
$.slot := AccessControlStorageLocation
}
}
modifier onlyRole(bytes32 _role) {
address account = _msgSender();
if (!_getAccessControlStorage()._roles[_role].hasRole[account])
revert IAccessControl.AccessControlUnauthorizedAccount(account, _role);
_;
}
modifier notPaused(FermionTypes.PausableRegion _region) {
// Region enum value must be used as the exponent in a power of 2
uint256 powerOfTwo = 1 << uint256(_region);
if ((FermionStorage.protocolStatus().paused & powerOfTwo) == powerOfTwo)
revert PauseErrors.RegionPaused(_region);
_;
}
/** Checks if the caller is the F-NFT contract owning the token.
*
* Reverts if:
* - The caller is not the F-NFT contract owning the token
*
* @param _offerId - offer ID associated with the vault
* @param pl - the number of tokens to add to the vault
*/
function verifyFermionFNFTCaller(uint256 _offerId, FermionStorage.ProtocolLookups storage pl) internal view {
if (msg.sender != pl.offerLookups[_offerId].fermionFNFTAddress)
revert FermionGeneralErrors.AccessDenied(msg.sender); // not using _msgSender() since the FNFT will never use meta transactions
}
}
"
},
"contracts/protocol/bases/mixins/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import { ContextLib } from "../../libs/ContextLib.sol";
/**
* @title Execution context
*
* @notice Provides the message sender
*/
contract Context {
/**
* @notice Returns the message sender address.
*
* @dev Could be msg.sender or the message sender address from storage (in case of meta transaction).
*
* @return the message sender address
*/
function _msgSender() internal view virtual returns (address) {
return ContextLib._msgSender();
}
}
"
},
"contracts/protocol/bases/mixins/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.8.24;
/**
* @title ReentrancyGuard
*
* @notice Prevent reeentrancy on a facet function level
*/
contract ReentrancyGuard {
uint256 internal constant GUARD_SLOT = 0;
uint256 internal constant GUARD_LOCKED = 1;
uint256 internal constant GUARD_UNLOCKED = 0;
uint256 internal constant REVERT_DATA_OFFSET = 0x1c;
uint256 internal constant REVERT_DATA_SIZE = 0x04;
bytes4 internal constant REENTRANCY_ERROR_SELECTOR = 0xb5dfd9e5;
error Reentered();
modifier nonReentrant() {
// NB: it's more optimal to compare msg.sender to address(this) twice than storing it in a variable (e.g. _isSelf)
// - it's cheaper
// - it does not add a variable to the stack and cause stack too deep errors
if (msg.sender != address(this)) {
assembly {
if tload(GUARD_SLOT) {
mstore(0, REENTRANCY_ERROR_SELECTOR)
revert(REVERT_DATA_OFFSET, REVERT_DATA_SIZE)
}
tstore(GUARD_SLOT, GUARD_LOCKED)
}
}
_;
// Unlocks the guard, making the pattern composable.
// After the function exits, it can be called again, even in the same transaction.
if (msg.sender != address(this)) {
assembly {
tstore(GUARD_SLOT, GUARD_UNLOCKED)
}
}
}
}
"
},
"contracts/protocol/domain/Constants.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;
import { FermionTypes } from "./Types.sol";
// Access Control Roles
bytes32 constant ADMIN = keccak256("ADMIN"); // Role Admin
bytes32 constant PAUSER = keccak256("PAUSER"); // Role for pausing the protocol
bytes32 constant UPGRADER = keccak256("UPGRADER"); // Role for performing contract and config upgrades
bytes32 constant FEE_COLLECTOR = keccak256("FEE_COLLECTOR"); // Role for collecting fees from the protocol
uint256 constant BYTE_SIZE = 8;
uint256 constant SLOT_SIZE = 32;
uint256 constant BOSON_DR_ID_OFFSET = 2; // Boson DR id is 2 higher than the seller id
uint256 constant HUNDRED_PERCENT = 100_00;
uint256 constant AUCTION_END_BUFFER = 15 minutes;
uint256 constant MINIMAL_BID_INCREMENT = 10_00; // 10%
// Fractionalization
uint256 constant MIN_FRACTIONS = 1e18;
uint256 constant MAX_FRACTIONS = 1 << 127;
// buyout exit price governance update
uint256 constant MIN_QUORUM_PERCENT = 20_00; // 20% is the minumum quorum percent for DAO exit price update
uint256 constant MIN_GOV_VOTE_DURATION = 1 days;
uint256 constant MAX_GOV_VOTE_DURATION = 7 days;
uint256 constant DEFAULT_GOV_VOTE_DURATION = 3 days;
// Default parameters
uint256 constant TOP_BID_LOCK_TIME = 3 days;
uint256 constant AUCTION_DURATION = 5 days;
uint256 constant UNLOCK_THRESHOLD = 50_00; // 50%
// Forceful fractionalisation
uint256 constant DEFAULT_FRACTION_AMOUNT = 1e5 * MIN_FRACTIONS;
uint256 constant PARTIAL_THRESHOLD_MULTIPLIER = 12;
uint256 constant LIQUIDATION_THRESHOLD_MULTIPLIER = 2;
uint256 constant PARTIAL_AUCTION_DURATION_DIVISOR = 4;
FermionTypes.EntityRole constant ANY_ENTITY_ROLE = FermionTypes.EntityRole(0);
"
},
"contracts/protocol/domain/Errors.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;
import { FermionTypes } from "./Types.sol";
import "seaport-types/src/lib/ConsiderationStructs.sol" as SeaportTypes;
interface FermionGeneralErrors {
// General errors
error InvalidAddress();
error ArrayLengthMismatch(uint256 expectedLength, uint256 actualLength);
error AccessDenied(address caller);
error InvalidPercentage(uint256 percentage);
error ZeroNotAllowed();
error UnexpectedDataReturned(bytes data);
// Array elements that are not in ascending order (i.e arr[i-1] > arr[i])
error NonAscendingOrder();
error InvalidTokenId(address fnftAddress, uint256 tokenId);
error InvalidPeriod();
}
interface InitializationErrors {
// Initialization errors
error DirectInitializationNotAllowed();
error VersionMustBeSet();
error AddressesAndCalldataLengthMismatch(uint256 addressesLength, uint256 calldataLength);
}
interface EntityErrors {
// Entity errors
error EntityAlreadyExists();
error NoSuchEntity(uint256 entityId);
error NotRoleManager(address manager, uint256 entityId, FermionTypes.EntityRole role);
error NotEntityWideRole(address account, uint256 entityId, FermionTypes.AccountRole role);
error NotAdmin(uint256 entityId, address admin);
error AlreadyAdmin(uint256 entityId, address admin);
error EntityHasNoRole(uint256 entityId, FermionTypes.EntityRole role);
error AccountHasNoRole(
uint256 entityId,
address account,
FermionTypes.EntityRole entityRole,
FermionTypes.AccountRole accountRole
);
error ChangeNotAllowed();
error NotSellersFacilitator(uint256 sellerId, uint256 facilitatorId);
error AssociatedEntityAlreadyExists(
FermionTypes.AssociatedRole associatedRole,
uint256 sellerId,
uint256 associatedEntityId
);
error NoEntitiesModified(FermionTypes.AssociatedRole associatedRole, uint256 sellerId);
error AccountAlreadyExists(address account);
error NewAccountSameAsOld();
}
interface OfferErrors {
// Offer errors
error InvalidQuantity(uint256 quantity);
error NoSuchOffer(uint256 offerId);
error InvalidOpenSeaOrder();
error NoPhygitalOffer(uint256 offerId);
error InvalidRoyaltyInfo();
error InvalidRoyaltyRecipient(address recipient);
error InvalidRoyaltyPercentage(uint256 percentage);
error OfferWithoutRoyalties(uint256 offerId);
error InvalidCustomItemPrice();
}
interface VerificationErrors {
// Verification errors
error VerificationTimeoutNotPassed(uint256 verificationTimeout, uint256 currentTime);
error VerificationTimeoutTooLong(uint256 verificationTimeout, uint256 maxVerificationTimeout);
error EmptyMetadata();
error DigestMismatch(bytes32 expected, bytes32 actual);
error AlreadyVerified(FermionTypes.VerificationStatus status);
error InvalidTokenState(uint256 tokenId, FermionTypes.TokenState tokenState);
error PhygitalsAlreadyVerified(uint256 tokenId);
error PhygitalsDigestMismatch(uint256 tokenId, bytes32 expectedDigest, bytes32 actualDigest);
error PhygitalsVerificationMissing(uint256 tokenId);
error InexistentVerificationStatus();
error InvalidVerificationStatus();
}
interface CustodyErrors {
// Custody errors
error NotTokenBuyer(uint256 tokenId, address owner, address caller);
error InvalidTaxAmount();
error InvalidCheckoutRequestStatus(
uint256 tokenId,
FermionTypes.CheckoutRequestStatus expectedStatus,
FermionTypes.CheckoutRequestStatus actualStatus
);
error InsufficientVaultBalance(uint256 tokenId, uint256 required, uint256 available);
error UpdateRequestExpired(uint256 tokenId);
error UpdateRequestTooRecent(uint256 tokenId, uint256 waitTime);
error NoTokensInCustody(uint256 offerId);
error InvalidCustodianFeePeriod();
error TaxAmountExceedsMaximum(uint256 tokenId, uint256 taxAmount, uint256 maxTaxAmount);
}
interface AuctionErrors {
error InvalidBid(uint256 tokenId, uint256 minimalBid, uint256 bid);
error AuctionEnded(uint256 tokenId, uint256 endedAt);
error AuctionNotStarted(uint256 tokenId);
error AuctionOngoing(uint256 tokenId, uint256 validUntil);
error AuctionFinalized(uint256 tokenId);
error NoFractionsAvailable(uint256 tokenId);
error NoBids(uint256 tokenId);
error BidBelowExitPrice(uint256 tokenId, uint256 bid, uint256 exitPrice);
}
interface CustodianVaultErrors is AuctionErrors {
// Custodian vault
error InactiveVault(uint256 tokenId);
error PeriodNotOver(uint256 tokenId, uint256 periodEnd);
error InvalidPartialAuctionThreshold();
error InsufficientBalanceToFractionalise(uint256 tokenId, uint256 minimalDeposit);
}
interface FundsErrors {
// Funds errors
error WrongValueReceived(uint256 expected, uint256 actual);
error NativeNotAllowed();
error PriceTooLow(uint256 price, uint256 minimumPrice);
error ZeroDepositNotAllowed();
error NothingToWithdraw();
error TokenTransferFailed(address to, uint256 amount, bytes errorMessage);
error InsufficientAvailableFunds(uint256 availableFunds, uint256 requestedFunds);
error ERC721CheckFailed(address tokenAddress, bool erc721expected);
error ERC721TokenNotTransferred(address tokenAddress, uint256 tokenId);
error PhygitalsNotFound(uint256 tokenId, FermionTypes.Phygital phygital);
error NoNativeFundsToClaim();
}
interface PauseErrors {
// Pause handler
error NotPaused();
error RegionPaused(FermionTypes.PausableRegion region);
}
interface MetaTransactionErrors {
// Meta transaction errors
error NonceUsedAlready();
error FunctionNotAllowlisted();
error InvalidFunctionName();
error FunctionCallFailed();
}
interface SignatureErrors {
error InvalidSignature(); // Somethihing is wrong with the signature
error SignatureValidationFailed(); // Signature might be correct, but the validation failed
error InvalidSigner(address expected, address actual);
}
interface FractionalisationErrors is AuctionErrors {
// Fractionalisation errors
error InvalidLength();
error InvalidFractionsAmount(uint256 amount, uint256 min, uint256 max);
error InvalidExitPrice(uint256 amount);
error AlreadyFractionalized(uint256 tokenId);
error PriceOracleNotWhitelisted(address oracleAddress);
error NotMaxBidder(uint256 tokenId, address caller, address winner);
error AlreadyRedeemed(uint256 tokenId);
error NoFractions();
error InvalidValue(uint256 expected, uint256 actual);
error BidRemovalNotAllowed(uint256 tokenId);
error MaxBidderCannotVote(uint256 tokenId);
error NotEnoughLockedVotes(uint256 tokenId, uint256 lockedVotes, uint256 requestedVotes);
error InitialFractionalisationOnly();
error MissingFractionalisation();
error InvalidAmount();
error TokenNotFractionalised(uint256 tokenId);
error InvalidAuctionIndex(uint256 auctionIndex, uint256 numberOfAuctions); // auctionIndex should be less than numberOfAuctions
error AuctionReserved(uint256 tokenId);
error ProposalNotActive(uint256 proposalId);
error AlreadyVoted();
error NoVotingPower(address voter);
error OnlyFractionOwner();
error InvalidVoteDuration(uint256 voteDuration);
error AlreadyVotedInProposal(uint256 proposalId);
error InvalidProposalId();
error ConflictingVote();
error OracleInternalError();
error AlreadyMigrated(address owner);
}
interface PriceOracleRegistryErrors {
error InvalidOracleAddress();
error InvalidIdentifier();
error OracleAlreadyApproved();
error OracleNotApproved();
error OracleValidationFailed();
error OracleReturnedInvalidPrice();
}
interface WrapperErrors {
error ZeroPriceNotAllowed();
error InvalidOrder(uint256 tokenId, SeaportTypes.OrderComponents order);
error InvalidOwner(uint256 tokenId, address expected, address actual);
error InvalidUnwrap();
error InvalidOpenSeaFee(uint256 actual, uint256 expected);
error UnsuccessfulExternalCall();
}
interface SafeERC20Errors {
error SafeERC20FailedOperation(address token);
}
interface FermionErrors is
FermionGeneralErrors,
InitializationErrors,
EntityErrors,
OfferErrors,
VerificationErrors,
CustodyErrors,
CustodianVaultErrors,
FundsErrors,
PauseErrors,
MetaTransactionErrors,
FractionalisationErrors,
SignatureErrors,
PriceOracleRegistryErrors,
WrapperErrors,
SafeERC20Errors
{}
"
},
"contracts/protocol/domain/Types.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;
/**
* @title FermionTypes
*
* @notice Enums and structs used by the Fermion Protocol contract ecosystem.
*/
contract FermionTypes {
enum EntityRole {
Seller,
Buyer,
Verifier,
Custodian,
RoyaltyRecipient
}
// Make at most 8 roles so they can be compacted into a byte
enum AccountRole {
Manager,
Assistant,
Treasury
}
enum AssociatedRole {
Facilitator,
RoyaltyRecipient
}
enum VerificationStatus {
Verified,
Rejected,
Pending
}
enum CheckoutRequestStatus {
None,
CheckedIn,
CheckOutRequested,
CheckOutRequestCleared,
CheckedOut
}
enum PausableRegion {
Config,
MetaTransaction,
Funds,
Entity,
Offer,
Verification,
Custody,
CustodyVault
}
enum AuctionState {
NotStarted,
Ongoing,
Reserved,
Finalized,
Redeemed
}
enum TokenState {
Inexistent,
Wrapped,
Unwrapping,
Unverified,
Verified,
CheckedIn,
CheckedOut,
Burned
}
enum PriceUpdateProposalState {
NotInit, // Explicitly represents an uninitialized state
Active,
Executed,
Failed
}
enum WrapType {
SELF_SALE,
OS_AUCTION,
OS_FIXED_PRICE
}
struct EntityData {
address admin;
uint256 roles;
string metadataURI;
}
struct MetaTransaction {
uint256 nonce;
address from;
address contractAddress;
string functionName;
bytes functionSignature;
}
struct Metadata {
string URI;
string hash;
}
struct Offer {
uint256 sellerId;
uint256 sellerDeposit;
uint256 verifierId;
uint256 verifierFee;
uint256 custodianId;
CustodianFee custodianFee;
uint256 facilitatorId;
uint256 facilitatorFeePercent;
address exchangeToken;
bool withPhygital;
Metadata metadata;
RoyaltyInfo royaltyInfo;
}
struct CustodianFee {
uint256 amount;
uint256 period;
}
struct CheckoutRequest {
CheckoutRequestStatus status;
address buyer;
uint256 taxAmount;
}
struct CustodianUpdateRequest {
uint256 newCustodianId;
CustodianFee custodianFee;
CustodianVaultParameters custodianVaultParameters;
uint256 requestTimestamp;
}
struct CustodianVaultParameters {
uint256 partialAuctionThreshold;
uint256 partialAuctionDuration;
uint256 liquidationThreshold;
uint256 newFractionsPerAuction;
}
struct FractionAuction {
uint256 endTime;
uint256 availableFractions;
uint256 maxBid;
uint256 bidderId;
}
// Fermion F-NFT, buyout auction
struct AuctionDetails {
uint256 timer;
uint256 maxBid;
address maxBidder;
uint256 totalFractions;
uint256 lockedFractions;
uint256 lockedBidAmount;
AuctionState state;
}
struct Votes {
uint256 total;
mapping(address => uint256) individual;
}
struct BuyoutAuctionStorage {
uint256 nftCount; // number of fractionalised NFTs
address exchangeToken;
BuyoutAuctionParameters auctionParameters;
uint256 pendingRedeemableSupply; // for tokens that auction started but not finalized yet
uint256 unrestricedRedeemableSupply;
uint256 unrestricedRedeemableAmount;
uint256 lockedRedeemableSupply;
mapping(uint256 => TokenAuctionInfo) tokenInfo;
address priceOracle;
mapping(address => PriceUpdateVoter) voters;
PriceUpdateProposal[] priceUpdateProposals;
}
struct TokenAuctionInfo {
bool isFractionalised;
Auction[] auctions;
int256[] lockedProceeds; // locked for users that voted to start
}
struct BuyoutAuctionParameters {
uint256 exitPrice;
uint256 duration; // in seconds; if zero, the default value is used
uint256 unlockThreshold; // in percents; if zero, the default value is used
uint256 topBidLockTime; // in seconds; if zero, the default value is used
}
/// @custom:storage-location erc7201:fermion.fractions.storage
struct FermionFractionsStorage {
// Array of ERC20 clone addresses, index is the epoch
address[] epochToClone;
uint256 currentEpoch;
mapping(address => bool) migrated;
}
struct PriceUpdateProposal {
uint256 newExitPrice;
uint256 votingDeadline;
uint256 quorumPercent; // in bps (e.g. 2000 is 20%)
uint256 yesVotes;
uint256 noVotes;
PriceUpdateProposalState state;
}
struct PriceUpdateVoter {
uint256 proposalId; // Tracks the ID of the proposal the voter last voted on
bool votedYes;
uint256 voteCount;
}
struct Auction {
AuctionDetails details;
Votes votes;
}
struct SplitProposal {
uint16 buyer;
uint16 seller;
bool matching;
}
struct Phygital {
address contractAddress;
uint256 tokenId;
}
struct TokenMetadata {
string name;
string symbol;
}
struct RoyaltyInfo {
address payable[] recipients;
uint256[] bps;
}
struct RoyaltyRecipientInfo {
address payable wallet;
uint256 minRoyaltyPercentage;
}
}
"
},
"contracts/protocol/facets/MetaTransaction.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;
import { ADMIN, SLOT_SIZE } from "../domain/Constants.sol";
import { MetaTransactionErrors } from "../domain/Errors.sol";
import { FermionTypes } from "../domain/Types.sol";
import { FermionStorage } from "../libs/Storage.sol";
import { Access } from "../bases/mixins/Access.sol";
import { IMetaTransactionEvents } from "../interfaces/events/IMetaTransactionEvents.sol";
import { EIP712 } from "../libs/EIP712.sol";
import { IFermionFNFT } from "../interfaces/IFermionFNFT.sol";
/**
* @title MetaTransactionFacet
*
* @notice Handles meta-transaction requests.
*/
contract MetaTransactionFacet is Access, EIP712, MetaTransactionErrors, IMetaTransactionEvents {
bytes32 private constant META_TRANSACTION_TYPEHASH =
keccak256(
bytes(
"MetaTransaction(uint256 nonce,address from,address contractAddress,string functionName,bytes functionSignature)"
)
);
/**
* @notice Constructor.
* Store the immutable values and build the domain separator.
*
* @param _fermionProtocolAddress - the address of the Fermion Protocol contract
*/
constructor(address _fermionProtocolAddress) EIP712(_fermionProtocolAddress) {}
/**
* @notice Initializes Facet.
* This function is callable only once.
*
* @param _functionNameHashes - a list of hashed function names (keccak256)
*/
function init(bytes32[] calldata _functionNameHashes) external {
setAllowlistedFunctionsInternal(_functionNameHashes, true);
FermionStorage.metaTransaction().fermionAddress = address(this);
}
/**
* @notice Handles the incoming meta transaction.
*
* Reverts if:
* - Metatransaction region is paused
* - Nonce is already used by the msg.sender for another transaction
* - Function is not allowlisted to be called using metatransactions
* - Function name does not match the bytes4 version of the function signature
* - Any code executed in the signed transaction reverts
* - The signature verification fails (see EIP712.verify for details)
*
* @param _userAddress - the sender of the transaction
* @param _functionName - the name of the function to be executed
* @param _functionSignature - the function signature
* @param _nonce - the nonce value of the transaction
* @param _sig - meta transaction signature.
If the user is ordinary EOA, it must be ECDSA signature in the format of concatenated r,s,v values.
If the user is a contract, it must be a valid ERC1271 signature.
If the user is a EIP-7702 smart account, it can be either a valid ERC1271 signature or a valid ECDSA signature.
* @param _offerIdWithEpoch - determines where the call is forwarded to. 0 is for Fermion Protocol,
* a plain offerId is for FermionFNFT associated with offerId, and {epoch+1}{offerId} is for FermionFractions
* associated with offerId and epoch.
*/
function executeMetaTransaction(
address _userAddress,
string calldata _functionName,
bytes calldata _functionSignature,
uint256 _nonce,
bytes calldata _sig,
uint256 _offerIdWithEpoch
) external payable notPaused(FermionTypes.PausableRegion.MetaTransaction) nonReentrant returns (bytes memory) {
address userAddress = _userAddress; // stack too deep workaround.
validateTx(_functionName, _functionSignature, _nonce, userAddress);
FermionTypes.MetaTransaction memory metaTx;
metaTx.nonce = _nonce;
metaTx.from = userAddress;
metaTx.contractAddress = getContractAddress(_offerIdWithEpoch);
metaTx.functionName = _functionName;
metaTx.functionSignature = _functionSignature;
verify(userAddress, hashMetaTransaction(metaTx), _sig);
return executeTx(metaTx.contractAddress, userAddress, _functionName, _functionSignature, _nonce);
}
/**
* @notice Gets the destination contract address from the storage.
*
* If the offerIdWithEpoch is 0, returns the address of this contract.
* If upper 128 bits are 0, returns the address of the FermionFNFT contract.
* Otherwise, subtracts 1 from upper 128 bits to get the epoch and
* returns the address of the corresponding ERC20 clone.
*
* @param _offerIdWithEpoch - determines where the call is forwarded to. 0 is for Fermion Protocol,
* a plain offerId is for FermionFNFT associated with offerId, and {epoch+1}{offerId} is for FermionFractions
* associated with offerId and epoch.
*/
function getContractAddress(uint256 _offerIdWithEpoch) internal view returns (address) {
if (_offerIdWithEpoch == 0) return address(this);
uint256 epoch = _offerIdWithEpoch >> 128;
uint256 offerId = _offerIdWithEpoch & type(uint128).max;
address FNFTAddress = FermionStorage.protocolLookups().offerLookups[offerId].fermionFNFTAddress;
if (epoch == 0) return FNFTAddress;
return IFermionFNFT(FNFTAddress).getERC20FractionsClone(epoch - 1);
}
/**
* @notice Checks nonce and returns true if used already for a specific address.
*
* @param _associatedAddress the address for which the nonce should be checked
* @param _nonce - the nonce that we want to check.
* @return true if nonce has already been used
*/
function isUsedNonce(address _associatedAddress, uint256 _nonce) external view returns (bool) {
return FermionStorage.metaTransaction().usedNonce[_associatedAddress][_nonce];
}
/**
* @notice Manages allow list of functions that can be executed using metatransactions.
*
* Emits a FunctionsAllowlisted event if successful.
*
* Reverts if:
* - Metatransaction region is paused
* - Caller is not a protocol admin
*
* @param _functionNameHashes - a list of hashed function names (keccak256)
* @param _isAllowlisted - new allowlist status
*/
function setAllowlistedFunctions(
bytes32[] calldata _functionNameHashes,
bool _isAllowlisted
) external onlyRole(ADMIN) notPaused(FermionTypes.PausableRegion.MetaTransaction) nonReentrant {
setAllowlistedFunctionsInternal(_functionNameHashes, _isAllowlisted);
}
/**
* @notice Tells if function can be executed as meta transaction or not.
*
* @param _functionNameHash - hashed function name (keccak256)
* @return isAllowlisted - allowlist status
*/
function isFunctionAllowlisted(bytes32 _functionNameHash) external view returns (bool isAllowlisted) {
return FermionStorage.metaTransaction().isAllowlisted[_functionNameHash];
}
/**
* @notice Tells if function can be executed as meta transaction or not.
*
* @param _functionName - function name
* @return isAllowlisted - allowlist status
*/
function isFunctionAllowlisted(string calldata _functionName) external view returns (bool isAllowlisted) {
return FermionStorage.metaTransaction().isAllowlisted[keccak256(abi.encodePacked(_functionName))];
}
/**
* @notice Validates the nonce and function signature.
*
* Reverts if:
* - Nonce is already used by the msg.sender for another transaction
* - Function is not allowlisted to be called using metatransactions
* - Function name does not match the bytes4 version of the function signature
*
* @param _functionName - the function name that we want to execute
* @param _functionSignature - the function signature
* @param _nonce - the nonce value of the transaction
* @param _userAddress - the sender of the transaction
*/
function validateTx(
string calldata _functionName,
bytes calldata _functionSignature,
uint256 _nonce,
address _userAddress
) internal view {
FermionStorage.MetaTransaction storage mt = FermionStorage.metaTransaction();
// Nonce should be unused
if (mt.usedNonce[_userAddress][_nonce]) revert NonceUsedAlready();
// Function must be allowlisted
bytes32 functionNameHash = keccak256(abi.encodePacked(_functionName));
if (!mt.isAllowlisted[functionNameHash]) revert FunctionNotAllowlisted();
// Function name must correspond to selector
bytes4 destinationFunctionSig = bytes4(_functionSignature);
bytes4 functionNameSig = bytes4(functionNameHash);
if (destinationFunctionSig != functionNameSig) revert InvalidFunctionName();
}
/**
* @notice Returns hashed meta transaction.
*
* @param _metaTx - the meta-transaction struct.
* @return the hash of the meta-transaction details
*/
function hashMetaTransaction(FermionTypes.MetaTransaction memory _metaTx) internal pure returns (bytes32) {
return
keccak256(
abi.encode(
META_TRANSACTION_TYPEHASH,
_metaTx.nonce,
_metaTx.from,
_metaTx.contractAddress,
keccak256(bytes(_metaTx.functionName)),
keccak256(_metaTx.functionSignature)
)
);
}
/**
* @notice Executes the meta transaction.
*
* Reverts if:
* - Any code executed in the signed transaction reverts
*
* @param _contractAddress - the address of the contract to be called, either this contract or one of FermionFNFTs
* @param _userAddress - the sender of the transaction
* @param _functionName - the name of the function to be executed
* @param _functionSignature - the function signature
* @param _nonce - the nonce value of the transaction
*/
function executeTx(
address _contractAddress,
address _userAddress,
string calldata _functionName,
bytes calldata _functionSignature,
uint256 _nonce
) internal returns (bytes memory) {
// Store the nonce provided to avoid playback of the same tx
FermionStorage.metaTransaction().usedNonce[_userAddress][_nonce] = true;
// Invoke local function with an external call
(bool success, bytes memory returnData) = _contractAddress.call{ value: msg.value }(
abi.encodePacked(_functionSignature, _userAddress)
);
// If error, return error message
if (!success) {
if (returnData.length > 0) {
// bubble up the error
assembly {
revert(add(SLOT_SIZE, returnData), mload(returnData))
}
} else {
// Reverts with default message
revert FunctionCallFailed();
}
}
emit MetaTransactionExecuted(_userAddress, msg.sender, _functionName, _nonce);
return returnData;
}
/**
* @notice Internal function that manages allow list of functions that can be executed using metatransactions.
*
* Emits a FunctionsAllowlisted event if successful.
*
* @param _functionNameHashes - a list of hashed function names (keccak256)
* @param _isAllowlisted - new allowlist status
*/
function setAllowlistedFunctionsInternal(bytes32[] calldata _functionNameHashes, bool _isAllowlisted) private {
FermionStorage.MetaTransaction storage mt = FermionStorage.metaTransaction();
// set new values
for (uint256 i = 0; i < _functionNameHashes.length; ) {
mt.isAllowlisted[_functionNameHashes[i]] = _isAllowlisted;
unchecked {
i++;
}
}
// Notify external observers
emit FunctionsAllowlisted(_functionNameHashes, _isAllowlisted, _msgSender());
}
}
"
},
"contracts/protocol/interfaces/events/IFermionFractionsEvents.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;
import { FermionTypes } from "../../domain/Types.sol";
/**
* @title IFermionFractionsEvents
*
* @notice Defines events related fractions and buyout auctions.
*/
interface IFermionFractionsEvents {
event Bid(
uint256 indexed tokenId,
address indexed from,
uint256 newPrice,
uint256 fractionsCount,
uint256 bidAmount,
uint256 epoch
);
event Redeemed(uint256 indexed tokenId, address indexed from, uint256 epoch);
event Claimed(address indexed from, uint256 fractionsBurned, uint256 amountClaimed, uint256 epoch);
event Voted(uint256 indexed tokenId, address indexed from, uint256 fractionAmount, uint256 epoch);
event VoteRemoved(uint256 indexed tokenId, address indexed from, uint256 fractionAmount, uint256 epoch);
event AuctionStarted(uint256 indexed tokenId, uint256 endTime, uint256 epoch);
event Fractionalised(uint256 indexed tokenId, uint256 fractionsCount, uint256 epoch);
event FractionsSetup(
uint256 initialFractionsAmount,
FermionTypes.BuyoutAuctionParameters buyoutAuctionParameters,
uint256 epoch
);
event AdditionalFractionsMinted(uint256 additionalAmount, uint256 totalFractionsAmount, uint256 epoch);
// Buyout Exit Price Governance Update Events
event PriceUpdateProposalCreated(
uint256 indexed proposalId,
uint256 newExitPrice,
uint256 votingDeadline,
uint256 quorumRequired,
uint256 epoch
);
event PriceUpdateProposalFinalized(uint256 indexed proposalId, bool success, uint256 epoch);
event PriceUpdateVoted(
uint256 indexed proposalId,
address indexed voter,
uint256 voteCount,
bool votedYes,
uint256 epoch
);
event PriceUpdateVoteRemoved(
uint256 indexed proposalId,
address indexed voter,
uint256 votesRemoved,
bool votedYes,
uint256 epoch
);
event ExitPriceUpdated(uint256 newPrice, bool isOracleUpdate, uint256 epoch);
event FractionsMigrated(address owners, uint256 fractionBalance);
}
"
},
"contracts/protocol/interfaces/events/IMetaTransactionEvents.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;
/**
* @title IMetaTransactionEvents
*
* @notice Defines events related to meta-transactions.
*/
interface IMetaTransactionEvents {
event MetaTransactionExecuted(
address indexed wallet,
address indexed caller,
string indexed functionName,
uint256 nonce
);
event FunctionsAllowlisted(bytes32[] functionNameHashes, bool isAllowlisted, address indexed caller);
}
"
},
"contracts/protocol/interfaces/IBosonProtocol.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;
/**
* @title BosonInterface
*
* @notice Minimal interface to interact with the Boson Protocol.
* Interface methods are copied here instead of being imported from bosonprotocol because of pragma incompatibility.
*/
interface IBosonProtocol {
enum AuthTokenType {
None,
Custom,
Lens,
ENS
}
enum PriceType {
Static, // Default should always be at index 0. Never change this value.
Discovery
}
enum ExchangeState {
Committed,
Revoked,
Canceled,
Redeemed,
Completed,
Disputed
}
enum Side {
Ask,
Bid,
Wrapper // Side is not relevant from the protocol perspective
}
enum OfferCreator {
Seller, // Default should always be at index 0. Never change this value.
Buyer
}
struct Seller {
uint256 id;
address assistant;
address admin;
address clerk; // Deprecated. Kept for backwards compatibility.
address payable treasury;
bool active;
string metadataUri;
}
struct AuthToken {
uint256 tokenId;
AuthTokenType tokenType;
}
struct VoucherInitValues {
string contractURI;
uint256 royaltyPercentage;
bytes32 collectionSalt;
}
struct Collection {
address collectionAddress;
string externalId;
}
struct Buyer {
uint256 id;
address payable account;
bool active;
}
struct DisputeResolver {
uint256 id;
uint256 escalationResponsePeriod;
address assistant;
address admin;
address clerk; // Deprecated. Kept for backwards compatibility.
address payable treasury;
string metadataUri;
bool active;
}
struct DisputeResolverFee {
address tokenAddress;
string tokenName;
uint256 feeAmount;
}
struct Offer {
uint256 id;
uint256 sellerId;
uint256 price;
uint256 sellerDeposit;
uint256 buyerCancelPenalty;
uint256 quantityAvailable;
address exchangeToken;
PriceType priceType;
OfferCreator creator;
string metadataUri;
string metadataHash;
bool voided;
uint256 collectionIndex;
RoyaltyInfo[] royaltyInfo;
uint256 buyerId;
}
struct OfferDates {
uint256 validFrom;
uint256 validUntil;
uint256 voucherRedeemableFrom;
uint256 voucherRedeemableUntil;
}
struct OfferDurations {
uint256 disputePeriod;
uint256 voucherValid;
uint256 resolutionPeriod;
}
struct DRParameters {
uint256 disputeResolverId;
address payable mutualizerAddress;
}
struct RoyaltyInfo {
address payable[] recipients;
uint256[] bps;
}
struct Exchange {
uint256 id;
uint256 offerId;
uint256 buyerId;
uint256 finalizedDate;
ExchangeState state;
}
struct Voucher {
uint256 committedDate;
uint256 validUntilDate;
uint256 redeemedDate;
bool expired;
}
struct DisputeResolutionTerms {
uint256 disputeResolverId;
uint256 escalationResponsePeriod;
uint256 feeAmount;
uint256 buyerEscalationDeposit;
}
struct OfferFees {
uint256 protocolFee;
uint256 agentFee;
}
struct PriceDiscovery {
uint256 price;
Side side;
address priceDiscoveryContract;
address conduit;
bytes priceDiscoveryData;
}
/**
* @notice Creates a seller.
*
* @param _seller - the fully populated struct with seller id set to 0x0
* @param _authToken - optional AuthToken struct that specifies an AuthToken type and tokenId that the seller can use to do admin functions
* @param _voucherInitValues - the fully populated BosonTypes.VoucherInitValues struct
*/
function createSeller(
Seller memory _seller,
AuthToken calldata _authToken,
VoucherInitValues calldata _voucherInitValues
) external;
/**
* @notice Creates a buyer.
*
* @param _buyer - the fully populated struct with buyer id set to 0x0
*/
function createBuyer(Buyer memory _buyer) external;
/**
* @notice Creates a dispute resolver.
*
* @param _disputeResolver - the fully populated struct with dispute resolver id set to 0x0
* @param _disputeResolverFees - list of fees dispute resolver charges per token type. Zero address is native currency. See {BosonTypes.DisputeResolverFee}
* feeAmount will be ignored because protocol doesn't yet support fees yet but DR still needs to provide array of fees to choose supported tokens
* @param _sellerAllowList - list of ids of sellers that can choose this dispute resolver. If empty, there are no restrictions on which seller can choose it.
*/
function createDisputeResolver(
DisputeResolver memory _disputeResolver,
DisputeResolverFee[] calldata _disputeResolverFees,
uint256[] calldata _sellerAllowList
) external;
/**
* @notice Gets the next account id that can be assigned to an account.
*
* @dev Does not increment the counter.
*
* @return nextAccountId - the account id
*/
function getNextAccountId() external view returns (uint256 nextAccountId);
/**
* @notice Gets the details about all seller's collections.
* In case seller has too many collections and this runs out of gas, please use getSellersCollectionsPaginated.
*
* @param _sellerId - the id of the seller to check
* @return defaultVoucherAddress - the address of the default voucher contract for the seller
* @return additionalCollections - an array of additional collections that the seller has created
*/
function getSellersCollections(
uint256 _sellerId
) external view returns (address defaultVoucherAddress, Collection[] memory additionalCollections);
/**
* @notice Creates an offer.
*
*
* @param _offer - the fully populated struct with offer id set to 0x0 and voided set to false
* @param _offerDates - the fully populated offer dates struct
* @param _offerDurations - the fully populated offer durations struct
* @param _drParameters - the id of chosen dispute resolver (can be 0) and mutualizer address (0 for self-mutualization)
* @param _agentId - the id of agent
* @param _feeLimit - the maximum fee that seller is willing to pay per exchange (for static offers)
*/
function createOffer(
Offer memory _offer,
OfferDates calldata _offerDates,
OfferDurations calldata _offerDurations,
DRParameters calldata _drParameters,
uint256 _agentId,
uint256 _feeLimit
) external;
/**
* @notice Adds DisputeResolverFees to an existing dispute resolver.
*
* @param _disputeResolverId - id of the dispute resolver
* @param _disputeResolverFees - list of fees dispute resolver charges per token type. Zero address is native currency. See {BosonTypes.DisputeResolverFee}
* feeAmount will be ignored because protocol doesn't yet support fees yet but DR still needs to provide array of fees to choose supported tokens
*/
function addFeesToDisputeResolver(
uint256 _disputeResolverId,
DisputeResolverFee[] calldata _disputeResolverFees
) external;
/**
* @notice Gets the next offer id.
*
* @dev Does not increment the counter.
*
* @return nextOfferId - the next offer id
*/
function getNextOfferId() external view returns (uint256 nextOfferId);
/**
* @notice Receives funds from the caller, maps funds to the seller id and stores them so they can be used during the commitToOffer.
*
* @param _sellerId - id of the seller that will be credited
* @param _tokenAddress - contract address of token that is being deposited (0 for native currency)
* @param _amount - amount to be credited
*/
function depositFunds(uint256 _sellerId, address _tokenAddress, uint256 _amount) external payable;
/**
* @notice Reserves a range of vouchers to be associated with an offer
*
* @param _offerId - the id of the offer
* @param _length - the length of the range
* @param _to - the address to send the pre-minted vouchers to (contract address or contract owner)
*/
function reserveRange(uint256 _offerId, uint256 _length, address _to) external;
/**
* @notice Gets the id that will be assigned to the next exchange.
*
* @return nextExchangeId - the next exchange id
*/
function getNextExchangeId() external view returns (uint256 nextExchangeId);
/**
* @notice Commits to a price discovery offer (first step of an exchange).
*
* @param _buyer - the buyer's address (caller can commit on behalf of a buyer)
* @param _tokenIdOrOfferId - the id of the offer to commit to or the id of the voucher (if pre-minted)
* @param _priceDiscovery - price discovery data (if applicable). See BosonTypes.PriceDiscovery
*/
function commitToPriceDiscoveryOffer(
address payable _buyer,
uint256 _tokenIdOrOfferId,
PriceDiscovery calldata _priceDiscovery
) external payable;
/**
* @notice Redeems a voucher.
*
* @param _exchangeId - the id of the exchange
*/
function redeemVoucher(uint256 _exchangeId) external;
/**
* @notice Completes an exchange.
*
* @param _exchangeId - the id of the exchange to complete
*/
function completeExchange(uint256 _exchangeId) external;
/**
* @notice Withdraws the specified funds. Can be called for seller, buyer or agent.
*
* @param _entityId - id of entity for which funds should be withdrawn
* @param _tokenList - list of contract addresses of tokens that are being withdrawn
* @param _tokenAmounts - list of amounts to be withdrawn, corresponding to tokens in tokenList
*/
function withdrawFunds(uint256 _entityId, address[] calldata _tokenList, uint256[] calldata _tokenAmounts) external;
/**
* @notice Gets the protocol fee percentage.
*
* @return the protocol fee percentage
*/
function getProtocolFeePercentage() external view returns (uint256);
/**
* @notice Gets the protocol fee percentage based on protocol fee table
*
* @dev This function calculates the protocol fee percentage for specific token and price.
* If the token has a custom fee table configured, it returns the corresponding fee percentage
* for the price range. If the token does not have a custom fee table, it falls back
* to the default protocol fee percentage.
*
* Reverts if the exchange token is BOSON.
*
* @param _exchangeToken - The address of the token being used for the exchange.
* @param _price - The price of the item or service in the exchange.
*
* @return the protocol fee percentage for given price and exchange token
*/
function getProtocolFeePercentage(address _exchangeToken, uint256 _price) external view returns (uint256);
/**
* @notice Retrieves the protocol fee percentage for a given exchange token and price.
*
* @dev This function calculates the protocol fee based on the token and price.
* If the token has a custom fee table, it applies the corresponding fee percentage
* for the price range. If the token does not have a custom fee table, it falls back
* to the default protocol fee percentage. If the exchange token is $BOSON,
* this function returns the flatBoson fee
*
* @param _exchangeToken - The address of the token being used for the exchange.
* @param _price - The price of the item or service in the exchange.
*
* @return The protocol fee amount based on the token and the price.
*/
function getProtocolFee(address _exchangeToken, uint256 _price) external view returns (uint256);
/**
* @notice Gets the flat protocol fee for exchanges in $BOSON.
*
* @return the flat fee taken for exchanges in $BOSON
*/
function getProtocolFeeFlatBoson() external view returns (uint256);
/**
* @notice Gets the Boson Token (ERC-20 contract) address.
*
* @return the Boson Token (ERC-20 contract) address
*/
function getTokenAddress() external view returns (address payable);
}
interface IBosonVoucher {
/**
* @notice Pre-mints all or part of an offer's reserved vouchers.
*
* For small offer quantities, this method may only need to be
* called once.
*
* But, if the range is large, e.g., 10k vouchers, block gas limit
* could cause the transaction to fail. Thus, in order to support
* a batched approach to pre-minting an offer's vouchers,
* this method can be called multiple times, until the whole
* range is minted.
*
* A benefit to the batched approach is that the entire reserved
* range for an offer need not be pre-minted at one time. A seller
* could just mint batches periodically, controlling the amount
* that are available on the market at any given time, e.g.,
* creating a pre-minted offer with a validity period of one year,
* causing the token range to be reserved, but only pre-minting
* a certain amount monthly.
*
* Caller must be contract owner (seller assistant address).
*
* Reverts if:
* - Offer id is not associated with a range
* - Amount to mint is more than remaining un-minted in range
* - Too many to mint in a single transaction, given current block gas limit
*
* @param _offerId - the id of the offer
* @param _amount - the amount to mint
*/
function preMint(uint256 _offerId, uint256 _amount) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
}
"
},
"contracts/protocol/interfaces/IFermionFNFT.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;
import { FermionTypes } from "../domain/Types.sol";
import { IFermionWrapper } from "../interfaces/IFermionWrapper.sol";
import { IFermionFractions } from "../interfaces/IFermionFractions.sol";
/**
* @title FermionWrapper interface
*
* A set of methods to interact with the FermionWrapper contract.
*/
interface IFermionFNFT is IFermionWrapper, IFermionFractions {
/**
* @notice Initializes the contract
*
* Reverts if:
* - Contract is already initialized
*
* @param _voucherAddress The address of the Boson Voucher contract
* @param _owner The address of the owner
* @param _exchangeToken The address of the exchange token
* @param _offerId The offer id
* @param _metadataUri The metadata URI, used for all tokens and contract URI
* @param _tokenMetadata - optional token metadata (name and symbol)
*/
function initialize(
address _voucherAddress,
address _owner,
address _exchangeToken,
uint256 _offerId,
string memory _metadataUri,
FermionTypes.TokenMetadata memory _tokenMetadata
) external;
/**
* @notice Burns the token and returns the voucher owner
*
* Reverts if:
* - Caller is not the Fermion Protocol
* - Token is not in the Unverified state
*
* @param _tokenId The token id.
*/
function burn(uint256 _tokenId) external returns (address wrappedVoucherOwner);
/**
* @notice Pushes the F-NFT from unverified to verified
*
* Reverts if:
* - Caller is not the Fermion Protocol
* - The new token state is not consecutive to the current state
*
* N.B. Not checking if the new state is valid, since the caller is the Fermion Protocol, which is trusted
*
* @param _tokenId The token id.
*/
function pushToNextTokenState(uint256 _tokenId, FermionTypes.TokenState _newState) external;
function tokenState(uint256 _tokenId) external view returns (FermionTypes.TokenState);
/**
* @notice Returns the address of the ERC20 clone for a specific epoch
* Users should interact with this contract directly for ERC20 operations
*
* @param _epoch The epoch
* @return The address of the ERC20 clone
*/
function getERC20FractionsClone(uint256 _epoch) external view returns (address);
/**
* @notice Returns the address of the ERC20 clone for the current epoch
* Users should interact with this contract directly for ERC20 operations
*
* @return The address of the ERC20 clone
*/
function getERC20FractionsClone() external view returns (address);
}
"
},
"contracts/protocol/interfaces/IFermionFractions.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.24;
import { FermionTypes } from "../domain/Types.sol";
import { IFermionFractionsEvents } from "./events/IFermionFractionsEvents.sol";
/**
* @title FermionWrapper interface
*
* A set
Submitted on: 2025-10-23 19:38:59
Comments
Log in to comment.
No comments yet.