// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.10;
/// @notice Minimal implementation of access control mechanism with two roles (owner & controller)
/// @dev Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol)
contract Controlled {
/*///////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error CallerIsNotAuthorized();
error TargetIsZeroAddress();
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event ControlTransferred(address indexed previousController, address indexed newController);
/*///////////////////////////////////////////////////////////////
ROLES STORAGE
//////////////////////////////////////////////////////////////*/
address private _owner;
address private _controller;
/*///////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor() {
_transferOwnership(msg.sender);
_transferControl(msg.sender);
}
/*///////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
modifier onlyOwner() {
if (_owner != msg.sender) revert CallerIsNotAuthorized();
_;
}
modifier onlyController() {
if (_controller != msg.sender) revert CallerIsNotAuthorized();
_;
}
modifier onlyOwnerOrController() {
if (_owner != msg.sender && _controller != msg.sender) revert CallerIsNotAuthorized();
_;
}
/*///////////////////////////////////////////////////////////////
VIEWS
//////////////////////////////////////////////////////////////*/
function owner() public view virtual returns (address) {
return _owner;
}
function controller() public view virtual returns (address) {
return _controller;
}
/*///////////////////////////////////////////////////////////////
ACTIONS
//////////////////////////////////////////////////////////////*/
function renounceOwnership() external virtual onlyOwner {
_transferOwnership(address(0));
}
function removeControl() external virtual onlyOwnerOrController {
_transferControl(address(0));
}
function transferOwnership(address newOwner) external virtual onlyOwner {
if (newOwner == address(0)) revert TargetIsZeroAddress();
_transferOwnership(newOwner);
}
function transferControl(address newController) external virtual onlyOwnerOrController {
if (newController == address(0)) revert TargetIsZeroAddress();
_transferControl(newController);
}
/*///////////////////////////////////////////////////////////////
INTERNAL LOGIC
//////////////////////////////////////////////////////////////*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
function _transferControl(address newController) internal virtual {
address oldController = _controller;
_controller = newController;
emit ControlTransferred(oldController, newController);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 indexed id);
event Approval(address indexed owner, address indexed spender, uint256 indexed id);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE/LOGIC
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
function tokenURI(uint256 id) public view virtual returns (string memory);
/*//////////////////////////////////////////////////////////////
ERC721 BALANCE/OWNER STORAGE
//////////////////////////////////////////////////////////////*/
mapping(uint256 => address) internal _ownerOf;
mapping(address => uint256) internal _balanceOf;
function ownerOf(uint256 id) public view virtual returns (address owner) {
require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
}
function balanceOf(address owner) public view virtual returns (uint256) {
require(owner != address(0), "ZERO_ADDRESS");
return _balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
ERC721 APPROVAL STORAGE
//////////////////////////////////////////////////////////////*/
mapping(uint256 => address) public getApproved;
mapping(address => mapping(address => bool)) public isApprovedForAll;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
}
/*//////////////////////////////////////////////////////////////
ERC721 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 id) public virtual {
address owner = _ownerOf[id];
require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");
getApproved[id] = spender;
emit Approval(owner, spender, id);
}
function setApprovalForAll(address operator, bool approved) public virtual {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function transferFrom(
address from,
address to,
uint256 id
) public virtual {
require(from == _ownerOf[id], "WRONG_FROM");
require(to != address(0), "INVALID_RECIPIENT");
require(
msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
"NOT_AUTHORIZED"
);
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
unchecked {
_balanceOf[from]--;
_balanceOf[to]++;
}
_ownerOf[id] = to;
delete getApproved[id];
emit Transfer(from, to, id);
}
function safeTransferFrom(
address from,
address to,
uint256 id
) public virtual {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes calldata data
) public virtual {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 id) internal virtual {
require(to != address(0), "INVALID_RECIPIENT");
require(_ownerOf[id] == address(0), "ALREADY_MINTED");
// Counter overflow is incredibly unrealistic.
unchecked {
_balanceOf[to]++;
}
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function _burn(uint256 id) internal virtual {
address owner = _ownerOf[id];
require(owner != address(0), "NOT_MINTED");
// Ownership check above ensures no underflow.
unchecked {
_balanceOf[owner]--;
}
delete _ownerOf[id];
delete getApproved[id];
emit Transfer(owner, address(0), id);
}
/*//////////////////////////////////////////////////////////////
INTERNAL SAFE MINT LOGIC
//////////////////////////////////////////////////////////////*/
function _safeMint(address to, uint256 id) internal virtual {
_mint(to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function _safeMint(
address to,
uint256 id,
bytes memory data
) internal virtual {
_mint(to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
}
/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721TokenReceiver {
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external virtual returns (bytes4) {
return ERC721TokenReceiver.onERC721Received.selector;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
interface IVotingEscrow {
struct LockedBalance {
int128 amount;
uint256 end;
}
function locked(address) external view returns (LockedBalance memory);
function balanceOf(address) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/Address.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.
*/
modifier initializer() {
bool isTopLevelCall = _setInitializedVersion(1);
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original
* initialization step. This is essential to configure modules that are added through upgrades and that require
* initialization.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*/
modifier reinitializer(uint8 version) {
bool isTopLevelCall = _setInitializedVersion(version);
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(version);
}
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*/
function _disableInitializers() internal virtual {
_setInitializedVersion(type(uint8).max);
}
function _setInitializedVersion(uint8 version) private returns (bool) {
// If the contract is initializing we ignore whether _initialized is set in order to support multiple
// inheritance patterns, but we only do this in the context of a constructor, and for the lowest level
// of initializers, because in other contexts the contract may have been reentered.
if (_initializing) {
require(
version == 1 && !Address.isContract(address(this)),
"Initializable: contract is already initialized"
);
return false;
} else {
require(_initialized < version, "Initializable: contract is already initialized");
_initialized = version;
return true;
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.10;
import {ERC20} from "@rari-capital/solmate/src/tokens/ERC20.sol";
import {ERC721, ERC721TokenReceiver} from "@rari-capital/solmate/src/tokens/ERC721.sol";
import {SafeTransferLib} from "@rari-capital/solmate/src/utils/SafeTransferLib.sol";
import {Controlled} from "../utils/Controlled.sol";
import {Renderer} from "./render/Renderer.sol";
/// @notice Non-fungible, limited-transferable token that grants citizen status in Nation3.
/// @author Nation3 (https://github.com/nation3/app/blob/main/contracts/contracts/passport/Passport.sol).
/// @dev Most ERC721 operations are restricted to controller contract.
/// @dev Is modified from the EIP-721 because of the lack of enough integration of the EIP-4973 at the moment of development.
/// @dev Token metadata is renderer on-chain through an external contract.
contract Passport is ERC721, Controlled {
/*///////////////////////////////////////////////////////////////
LIBRARIES
//////////////////////////////////////////////////////////////*/
using SafeTransferLib for ERC20;
/*///////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error NotMinted();
error NotAuthorized();
error InvalidFrom();
error NotSafeRecipient();
/*///////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
// @notice On-chain metadata renderer.
Renderer public renderer;
/// @dev Tracks the number of tokens minted & not burned.
uint256 internal _supply;
/// @dev Tracks the next id to mint.
uint256 internal _idTracker;
// @dev Timestamp of each token mint.
mapping(uint256 => uint256) internal _timestampOf;
// @dev Authorized address to sign messages in behalf of the passport holder, it can be different from the owner.
// @dev Could be used for IRL events authentication.
mapping(uint256 => address) internal _signerOf;
/*///////////////////////////////////////////////////////////////
VIEWS
//////////////////////////////////////////////////////////////*/
/// @notice Returns total number of tokens in supply.
function totalSupply() external view virtual returns (uint256) {
return _supply;
}
/// @notice Gets next id to mint.
function getNextId() external view virtual returns (uint256) {
return _idTracker;
}
/// @notice Returns the timestamp of the mint of a token.
/// @param id Token to retrieve timestamp from.
function timestampOf(uint256 id) public view virtual returns (uint256) {
if (_ownerOf[id] == address(0)) revert NotMinted();
return _timestampOf[id];
}
/// @notice Returns the authorized signer of a token.
/// @param id Token to retrieve signer from.
function signerOf(uint256 id) external view virtual returns (address) {
if (_ownerOf[id] == address(0)) revert NotMinted();
return _signerOf[id];
}
/// @notice Get encoded metadata from renderer.
/// @param id Token to retrieve metadata from.
function tokenURI(uint256 id) public view override returns (string memory) {
return renderer.render(id, ownerOf(id), timestampOf(id));
}
/*///////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/// @dev Sets name & symbol.
constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}
/*///////////////////////////////////////////////////////////////
USER ACTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Allows the owner of a passport to update the signer.
/// @param id Token to update the signer.
/// @param signer Address of the new signer account.
function setSigner(uint256 id, address signer) external virtual {
if (_ownerOf[id] != msg.sender) revert NotAuthorized();
_signerOf[id] = signer;
}
/*///////////////////////////////////////////////////////////////
CONTROLLED ACTIONS
//////////////////////////////////////////////////////////////*/
/// @notice ERC721 method to set allowance. Only allowed to controller.
/// @dev Prevent approvals on marketplaces & other contracts.
function approve(address spender, uint256 id) public override onlyController {
getApproved[id] = spender;
emit Approval(_ownerOf[id], spender, id);
}
/// @notice ERC721 method to set allowance. Only allowed to controller.
/// @dev Prevent approvals on marketplaces & other contracts.
function setApprovalForAll(address operator, bool approved) public override onlyController {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
/// @notice Allows controller to transfer a passport (id) between two addresses.
/// @param from Current owner of the token.
/// @param to Recipient of the token.
/// @param id Token to transfer.
function transferFrom(
address from,
address to,
uint256 id
) public override onlyController {
if (from != _ownerOf[id]) revert InvalidFrom();
if (to == address(0)) revert TargetIsZeroAddress();
unchecked {
_balanceOf[from]--;
_balanceOf[to]++;
}
_ownerOf[id] = to;
_timestampOf[id] = block.timestamp;
_signerOf[id] = to;
delete getApproved[id];
emit Transfer(from, to, id);
}
/// @notice Allows controller to safe transfer a passport (id) between two address.
/// @param from Curent owner of the token.
/// @param to Recipient of the token.
/// @param id Token to transfer.
function safeTransferFrom(
address from,
address to,
uint256 id
) public override onlyController {
transferFrom(from, to, id);
if (
to.code.length != 0 &&
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") !=
ERC721TokenReceiver.onERC721Received.selector
) revert NotSafeRecipient();
}
/// @notice Allows controller to safe transfer a passport (id) between two address.
/// @param from Curent owner of the token.
/// @param to Recipient of the token.
/// @param id Token to transfer.
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes calldata data
) public override onlyController {
transferFrom(from, to, id);
if (
to.code.length != 0 &&
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) !=
ERC721TokenReceiver.onERC721Received.selector
) revert NotSafeRecipient();
}
/// @notice Mints a new passport to the recipient.
/// @param to Token recipient.
/// @dev Id is auto assigned.
function mint(address to) external virtual onlyController returns (uint256 tokenId) {
tokenId = _idTracker;
_mint(to, tokenId);
// Realistically won't overflow;
unchecked {
_timestampOf[tokenId] = block.timestamp;
_signerOf[tokenId] = to;
_idTracker++;
_supply++;
}
}
/// @notice Mints a new passport to the recipient.
/// @param to Token recipient.
/// @dev Id is auto assigned.
function safeMint(address to) external virtual onlyController returns (uint256 tokenId) {
tokenId = _idTracker;
_safeMint(to, tokenId);
// Realistically won't overflow;
unchecked {
_timestampOf[tokenId] = block.timestamp;
_signerOf[tokenId] = to;
_idTracker++;
_supply++;
}
}
/// @notice Burns the specified token.
/// @param id Token to burn.
function burn(uint256 id) external virtual onlyController {
_burn(id);
// Would have reverted before if the token wasnt minted
unchecked {
delete _timestampOf[id];
delete _signerOf[id];
_supply--;
}
}
/*///////////////////////////////////////////////////////////////
ADMIN ACTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Allows the owner to update the renderer contract.
/// @param _renderer New renderer address.
function setRenderer(Renderer _renderer) external virtual onlyOwner {
renderer = _renderer;
}
/// @notice Allows the owner to withdraw any ERC20 sent to the contract.
/// @param token Token to withdraw.
/// @param to Recipient address of the tokens.
function recoverTokens(ERC20 token, address to) external virtual onlyOwner returns (uint256 amount) {
amount = token.balanceOf(address(this));
token.safeTransfer(to, amount);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.10;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {ERC20} from "@rari-capital/solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from "@rari-capital/solmate/src/utils/SafeTransferLib.sol";
import {IVotingEscrow} from "../governance/IVotingEscrow.sol";
import {Passport} from "./Passport.sol";
/// @notice Manage the issuance of Passport tokens.
/// @author Nation3 (https://github.com/nation3/app/blob/main/contracts/contracts/passport/PassportIssuer.sol).
contract PassportIssuer is Initializable, Ownable {
/*///////////////////////////////////////////////////////////////
LIBRARIES
//////////////////////////////////////////////////////////////*/
using SafeTransferLib for ERC20;
using SafeTransferLib for IVotingEscrow;
/*///////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error NotEligible();
error NonRevocable();
error InvalidSignature();
error PassportAlreadyIssued();
error PassportNotIssued();
error IssuanceIsDisabled();
error IssuancesLimitReached();
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @dev Events inspired by EIP-4973
event Attest(address indexed _to, uint256 indexed _tokenId);
event Revoke(address indexed _to, uint256 indexed _tokenId);
event UpdateRequirements(uint256 claimRequiredBalance, uint256 revokeUnderBalance);
/*///////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
/// @notice The token used to lock.
IVotingEscrow public veToken;
/// @notice The token that issues.
Passport public passToken;
/// @notice Status of the passport issance.
bool public enabled;
/// @notice Limit of passports to issue, cannot be change after initialization.
uint256 public maxIssuances;
/// @notice Number of passports issued.
uint256 public totalIssued;
/// @notice Balance of veToken required to claim.
uint256 public claimRequiredBalance;
/// @notice Balance of veToken under which a passport is revocable.
uint256 public revokeUnderBalance;
/// @notice Agreement statement to sign before claim.
string public statement;
/// @notice Agreement terms URL to sign before claim.
string public termsURI;
/// @dev Domain separator hash on initialization.
bytes32 internal INITIAL_DOMAIN_SEPARATOR;
/// @dev Map the passport status of an account: (0) Not issued, (1) Issued, (2) Revoked.
mapping(address => uint8) internal _status;
/// @dev Passport id issued by account.
mapping(address => uint256) internal _passportId;
/*///////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
/// @notice Requires to be enabled before performing function.
modifier isEnabled() {
if (!enabled) revert IssuanceIsDisabled();
_;
}
/*///////////////////////////////////////////////////////////////
INITIALITION
//////////////////////////////////////////////////////////////*/
/// @notice Sets tokens and supply.
/// @param _veToken Lock token which balance is required to claim.
/// @param _passToken Passport token to mint.
/// @param _maxIssuances Maximum number of tokens that can be issued with this contract.
function initialize(
IVotingEscrow _veToken,
Passport _passToken,
uint256 _maxIssuances
) external initializer {
veToken = _veToken;
passToken = _passToken;
maxIssuances = _maxIssuances;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*///////////////////////////////////////////////////////////////
VIEWS
//////////////////////////////////////////////////////////////*/
/// @notice Returns the status of an account: (0) Not issued, (1) Issued, (2) Revoked.
function passportStatus(address account) external view virtual returns (uint8) {
return _status[account];
}
/// @notice Returns passport id of a given account.
/// @param account Holder account of a passport.
/// @dev Revert if the account has no passport.
function passportId(address account) public view virtual returns (uint256) {
if (_status[account] == 0) revert PassportNotIssued();
return _passportId[account];
}
/*///////////////////////////////////////////////////////////////
USER ACTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Claims a new passport token with signature validation.
/// @param v Signature version.
/// @param r Signature fragment.
/// @param s Signature fragment.
function claim(
uint8 v,
bytes32 r,
bytes32 s
) external virtual isEnabled {
if (totalIssued >= maxIssuances) revert IssuancesLimitReached();
if (_status[msg.sender] > 0) revert PassportAlreadyIssued();
if (veToken.balanceOf(msg.sender) < claimRequiredBalance) revert NotEligible();
verifySignature(v, r, s);
_issue(msg.sender);
}
/// @notice Allows caller to renounce to the passport.
function renounce() external virtual {
_revoke(msg.sender);
}
/// @notice Revokes the passport of a given account if it's not eligible anymore.
function revoke(address account) external virtual {
if (veToken.balanceOf(account) >= revokeUnderBalance) revert NonRevocable();
_revoke(account);
}
/*///////////////////////////////////////////////////////////////
ADMIN ACTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Set requirements to claim & revoke.
/// @param _claimRequiredBalance Minimum amount of voting escrow tokens required for a new issuance.
/// @param _revokeUnderBalance Amount of voting escrow tokens under which a passport is revocable.
function setParams(uint256 _claimRequiredBalance, uint256 _revokeUnderBalance) external virtual onlyOwner {
claimRequiredBalance = _claimRequiredBalance;
revokeUnderBalance = _revokeUnderBalance;
emit UpdateRequirements(_claimRequiredBalance, _revokeUnderBalance);
}
/// @notice Updates issuance status.
/// @dev Can be used by the owner to halt the issuance of new passports.
function setEnabled(bool status) external virtual onlyOwner {
enabled = status;
}
/// @notice Sets the statement of the issuance agreement.
function setStatement(string memory _statement) external virtual onlyOwner {
statement = _statement;
}
/// @notice Sets the terms URI of the issuance agreement.
function setTermsURI(string memory _termsURI) external virtual onlyOwner {
termsURI = _termsURI;
}
/// @notice Allows the owner to revoke the passport of any account.
function adminRevoke(address account) external virtual onlyOwner {
_revoke(account);
}
/// @notice Allows the owner to withdraw any ERC20 sent to the contract.
/// @param token Token to withdraw.
/// @param to Recipient address of the tokens.
function recoverTokens(ERC20 token, address to) external virtual onlyOwner returns (uint256 amount) {
amount = token.balanceOf(address(this));
token.safeTransfer(to, amount);
}
/*///////////////////////////////////////////////////////////////
EIP-712 LOGIC
//////////////////////////////////////////////////////////////*/
/// @notice Returns the domain separator hash for EIP-712 signature.
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return INITIAL_DOMAIN_SEPARATOR != "" ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
/// @notice Verify if sender has signed the contract agreement following EIP-712.
/// @dev Reverts on signature missmatch.
function verifySignature(
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
// Unchecked because no math here
unchecked {
address signer = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256("Agreement(string statement,string termsURI)"),
keccak256(abi.encodePacked(statement)),
keccak256(abi.encodePacked(termsURI))
)
)
)
),
v,
r,
s
);
if (signer != msg.sender) revert InvalidSignature();
}
}
// @dev Compute the EIP-712 domain's separator of the contract.
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("PassportIssuer")),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*///////////////////////////////////////////////////////////////
INTERNAL LOGIC
//////////////////////////////////////////////////////////////*/
/// @dev Mints a new passport token for the recipient.
/// @param recipient Address to issue the passport to.
function _issue(address recipient) internal virtual {
// Mint a new passport to the recipient account
uint256 tokenId = passToken.safeMint(recipient);
// Realistically won't overflow;
unchecked {
totalIssued++;
}
_status[recipient] = 1;
_passportId[recipient] = tokenId;
emit Attest(recipient, tokenId);
}
/// @dev Burns the passport token of the account.
/// @param account Address of the account to revoke the passport to.
function _revoke(address account) internal virtual {
uint256 tokenId = passportId(account);
// Burn passport
passToken.burn(tokenId);
_status[account] = 2;
delete _passportId[account];
emit Revoke(account, tokenId);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.10;
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
contract Renderer {
function render(
uint256 tokenId,
address owner,
uint256 timestamp
) public view virtual returns (string memory tokenURI) {
string memory name = Strings.toString(uint256(uint160(owner)));
tokenURI = string(abi.encodePacked(Strings.toString(tokenId),'-',name,'-',Strings.toString(timestamp)));
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
}
{
"compilationTarget": {
"src/passport/PassportIssuer.sol": "PassportIssuer"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 2000
},
"remappings": []
}
[{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"IssuanceIsDisabled","type":"error"},{"inputs":[],"name":"IssuancesLimitReached","type":"error"},{"inputs":[],"name":"NonRevocable","type":"error"},{"inputs":[],"name":"NotEligible","type":"error"},{"inputs":[],"name":"PassportAlreadyIssued","type":"error"},{"inputs":[],"name":"PassportNotIssued","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":true,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"Attest","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","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":true,"internalType":"address","name":"_to","type":"address"},{"indexed":true,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"claimRequiredBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"revokeUnderBalance","type":"uint256"}],"name":"UpdateRequirements","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"adminRevoke","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimRequiredBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"enabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IVotingEscrow","name":"_veToken","type":"address"},{"internalType":"contract Passport","name":"_passToken","type":"address"},{"internalType":"uint256","name":"_maxIssuances","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxIssuances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"passToken","outputs":[{"internalType":"contract Passport","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"passportId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"passportStatus","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"recoverTokens","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"revoke","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"revokeUnderBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"status","type":"bool"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_claimRequiredBalance","type":"uint256"},{"internalType":"uint256","name":"_revokeUnderBalance","type":"uint256"}],"name":"setParams","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_statement","type":"string"}],"name":"setStatement","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_termsURI","type":"string"}],"name":"setTermsURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"statement","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"termsURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalIssued","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":[],"name":"veToken","outputs":[{"internalType":"contract IVotingEscrow","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"verifySignature","outputs":[],"stateMutability":"nonpayable","type":"function"}]