Contract Name:
SubscriptionManager
Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
/**
* Kekotron Subscription Manager
*
* Website: nchart.io
* Docs: docs.nchart.io
* twitter.com/Nchart_
* twitter.com/Kekotron_
*/
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import "@solady/auth/OwnableRoles.sol";
import "@solady/utils/LibMap.sol";
import {Nchart} from "./Nchart.sol";
import "./IUniswapV2Router02.sol";
/**
* ........
* ..::::::::::::. .
* .:::::::::::::::. =+-.
* --::::::::::::::::. =+++-
* *##*+::::::::::::::. =+++++
* *#####: .::::::::::. =++++++
* -######: .:::::::. =++++++-
* *######: :. .::::. =+++++++
* #######: -=-:. .:. =+++++++
* +######: -=====:. =++++++=
* :######: -========-. =++++++:
* +#####: -===========-.-+++++=
* =####: -==============-==+-
* :*##: -================-.
* :+: -==============-.
* :==========-:.
* ......
*
*
* @dev Contract which accepts ETH to pay for a subscription, which is used to buy and burn CHART from
* @dev the UniswapV2 LP Pool
*/
contract SubscriptionManager is OwnableRoles {
uint40 internal constant MAX_UINT40 = type(uint40).max;
uint256 public constant KEEPER_ROLE = uint256(1);
Nchart public immutable chart;
IUniswapV2Router02 public immutable router;
LibMap.Uint40Map private expiration;
uint256 public subscriptionPrice = 0.015 ether; // Price is 0.015e to start
uint256 public referralFeePriceReduction = 0.001 ether; // Reduction of fee per address if referral is provided
uint40 public subscriptionLength; // Lifetime subscription to start
// Percent of fees to use to buy and burn CHART
uint8 public burnPercent = 99;
// Percent of fees to send to referrer if they have an active subscription
uint8 public referralPercent = 5;
event BurnPercentUpdated(uint8 newPercent, uint8 oldPercent);
event ReferralPaid(address indexed referrer, uint256 amount);
event ReferralFeePriceReductionUpdated(uint256 newAmount, uint256 oldAmount);
event ReferralPercentUpdated(uint8 newPercent, uint8 oldPercent);
event SubscriptionLengthUpdated(uint256 newLength, uint256 oldLength);
event SubscriptionPaid(address indexed subscriber, uint40 expirationTimestamp, uint256 price);
event SubscriptionPriceUpdated(uint256 newPrice, uint256 oldPrice);
error SubscriptionManager__BurnPercentMustBeGreaterThan50();
error SubscriptionManager__BurnPercentMustBeLessThan100();
error SubscriptionManager__CannotReferSelf();
error SubscriptionManager__CannotReduceMoreThanSubscriptionPrice();
error SubscriptionManager__CannotRegisterAddressZero();
error SubscriptionManager__CanOnlyIncreaseExpiration();
error SubscriptionManager__ErrorRetrievingPriceFromDataFeed();
error SubscriptionManager__ErrorSendingKeeperFunds();
error SubscriptionManager__InvalidETHAmountProvided(uint256 msgValue, uint256 ethRequired);
error SubscriptionManager__MaxFiftyPercentReferralPercent();
error SubscriptionManager__MustProvideAtLeastOneAddress();
error SubscriptionManager__OnlyOwner();
error SubscriptionManager__UseRegisterAddressesFunction();
constructor(address payable chart_, address owner_, address router_) {
_initializeOwner(owner_);
chart = Nchart(chart_);
router = IUniswapV2Router02(router_);
}
receive() external payable {
revert SubscriptionManager__UseRegisterAddressesFunction();
}
fallback() external payable {
revert SubscriptionManager__UseRegisterAddressesFunction();
}
/**
* @notice Sets the length of the subscription period.
*
* @dev The subscription length is either added to the remaining time of a user's subscription or block.timestamp if unset
* @dev Passing in a value of 0 will set the subscription to unlimited, and expiration will be set to MAX_UINT40
* @dev - Throws if caller is not owner
*
* @dev On completion:
* @dev - `subscriptionLength` = `newSubscriptionLength`
* @dev - Emits {SubscriptionLengthUpdated} event
*
* @param newSubscriptionLength Length to update future subscriptions to
*/
function setSubscriptionLength(uint40 newSubscriptionLength) external {
_requireIsOwner();
uint40 oldSubscriptionLength = subscriptionLength;
subscriptionLength = newSubscriptionLength;
emit SubscriptionLengthUpdated(newSubscriptionLength, oldSubscriptionLength);
}
/**
* @notice Sets the percentage of fees to send to referrers if they have an active subscription
*
* @dev - Throws if `msg.sender` != `owner()`
* @dev - Throws if provided percent is > 5
*
* @dev On completion:
* @dev - `referralPercent` = `newPercent`
* @dev - Emits {ReferralPercentUpdated} event
*
* @param newPercent Percentage of fees to send to referrers
*/
function setReferralPercent(uint8 newPercent) external {
_requireIsOwner();
if (newPercent > 50) {
revert SubscriptionManager__MaxFiftyPercentReferralPercent();
}
uint8 oldPercent = referralPercent;
referralPercent = newPercent;
emit ReferralPercentUpdated(newPercent, oldPercent);
}
/**
* @notice Sets the amount fees are reduced per account when providing a referral
*
* @dev - Throws if `msg.sender` != `owner()`
* @dev - Throws if provided reduction amount is greater than subscription price
*
* @dev On completion:
* @dev - `referralFeePriceReduction` = `newAmount`
* @dev - Emits {ReferralFeePriceReductionUpdated} event
*
* @param newAmount Amount to reduce fees per address if referral provided
*/
function setReferralFeePriceReduction(uint256 newAmount) external {
_requireIsOwner();
if (newAmount >= subscriptionPrice) {
revert SubscriptionManager__CannotReduceMoreThanSubscriptionPrice();
}
uint256 oldReferralFeePriceReduction = referralFeePriceReduction;
referralFeePriceReduction = newAmount;
emit ReferralFeePriceReductionUpdated(newAmount, oldReferralFeePriceReduction);
}
/**
* @notice Sets the price of new subscriptions. Users will be charged this amount per address to use premium features
*
* @dev Price of subscription is set in ETH, stored in 1e18 (wei)
* @dev Passing in a value of 0 will set premium features to free.
* @dev - Throws if caller is not owner
*
* @dev On completion:
* @dev - `subscriptionPrice` = `newSubscriptionPrice`
* @dev - Emits {SubscriptionPriceUpdated} event
*
* @param newSubscriptionPrice Price to update future subscriptions to
*/
function setSubscriptionPrice(uint256 newSubscriptionPrice) external {
_requireIsOwner();
uint256 oldSubscriptionPrice = subscriptionPrice;
subscriptionPrice = newSubscriptionPrice;
emit SubscriptionPriceUpdated(newSubscriptionPrice, oldSubscriptionPrice);
}
/**
* @notice Sets the percentage of fees used to buy and burn CHART
*
* @dev - Throws if `msg.sender` != `owner()`
* @dev - Throws if provided percent is > 100
*
* @dev On completion:
* @dev - `burnPercent` = `newPercent`
* @dev - Emits {BurnPercentUpdated} event
*
* @param newPercent Percentage of fees to be used to buy and burn CHART
*/
function setBurnPercent(uint8 newPercent) external {
_requireIsOwner();
if (newPercent > 100) {
revert SubscriptionManager__BurnPercentMustBeLessThan100();
}
if (newPercent < 50) {
revert SubscriptionManager__BurnPercentMustBeGreaterThan50();
}
uint8 oldPercent = burnPercent;
burnPercent = newPercent;
emit BurnPercentUpdated(newPercent, oldPercent);
}
/**
* @notice Allows owner to increase the expiration timestamp for a provided user in case of
* @notice giveaways.
*
* @dev - Throws if `newExpiration` < current expiration
* @dev - Throws if `msg.sender` != `owner()
*
* @dev On completion:
* @dev - User expiration is set to `newExpiration`
*/
function setExpirationTimestamp(uint40 newExpiration, address user) external {
_requireIsOwner();
if (newExpiration < LibMap.get(expiration, uint256(uint160(user)))){
revert SubscriptionManager__CanOnlyIncreaseExpiration();
}
LibMap.set(expiration, uint256(uint160(user)), newExpiration);
}
/**
* @notice Grants the KEEPER_ROLE to the provided user.
*
* @dev - Throws if the `msg.sender` is not `owner()`
*
* @dev On completion:
* @dev - `newKeeper` is assigned the `KEEPER_ROLE`
*
* @param newKeeper Address to assign the `KEEPER_ROLE`
*/
function grantKeeperRole(address newKeeper) external {
grantRoles(newKeeper, KEEPER_ROLE);
}
/**
* @notice Revokes the KEEPER_ROLE from the provided user.
*
* @dev - Throws if the `msg.sender` is not `owner()`
*
* @dev On completion:
* @dev - `toRevoke` is no longer assigned the `KEEPER_ROLE`
*
* @param toRevoke Address to revoke the `KEEPER_ROLE` from
*/
function revokeKeeperRole(address toRevoke) external {
revokeRoles(toRevoke, KEEPER_ROLE);
}
/**
* @notice Registers a list of addresses for premium features. There is an optional referrer address
* @notice which will receive a percentage of the fees paid by the registered addresses if they have an active subscription.
*
* @dev - Throws if the length of the provided addresses is 0
* @dev - Throws if `msg.value` is not equal to the exact amount required to pay for the subscriptions
* @dev - Throws if any address provided is address(0)
* @dev - Throws if the referrer address executes code > 2300 gas
* @dev - If the provided referral address is not subscribed, it is a no-op
*
* @dev On completion:
* @dev - `expiration` mapping for each address is updated to add on an additional `subLength` seconds
* @dev - If `expiration` + the current length of subscriptions is > uint256 max, set to uint256 max
* @dev - If the current length of subscription is 0, set `expiration` to uint256 max
* @dev - The contract has `subLength` * `subPrice` - `referralAmount` more ETH
* @dev - If the referrer is subscribed, they receive `referralAmount` ETH
* @dev - Emits `addresses.length` {SubscriptionPaid} events
* @dev - Emits {ReferralPaid} event if referrer is subscribed
*
* @param addresses A list of addresses to register
* @param referrer Optional address of referrer
*/
function registerAddresses(address[] calldata addresses, address referrer) external payable {
uint256 numSubs = addresses.length;
if (numSubs == 0) {
revert SubscriptionManager__MustProvideAtLeastOneAddress();
}
if (referrer == msg.sender) {
revert SubscriptionManager__CannotReferSelf();
}
uint256 subPrice = subscriptionPrice;
uint256 referralFeeReduction = referrer != address(0) ? referralFeePriceReduction * numSubs : 0;
uint256 ethRequired = numSubs * subPrice;
uint256 referralAmount;
if (block.timestamp <= LibMap.get(expiration, uint256(uint160(referrer)))) {
if (referralPercent > 0) {
referralAmount = ethRequired * referralPercent / 100;
}
}
if (referralFeeReduction > 0) {
ethRequired -= referralFeeReduction;
}
if (msg.value != ethRequired) {
revert SubscriptionManager__InvalidETHAmountProvided(msg.value, ethRequired);
}
uint40 subLength = subscriptionLength;
if (subLength == 0) {
for (uint256 i = 0; i < numSubs;) {
address addr = addresses[i];
_requireValidAddress(addr);
LibMap.set(expiration, uint256(uint160(addr)), type(uint40).max);
emit SubscriptionPaid(addr, MAX_UINT40, subPrice);
unchecked {
++i;
}
}
} else {
uint40 maxExpiration = type(uint40).max - subLength;
for (uint256 i = 0; i < numSubs;) {
address addr = addresses[i];
_requireValidAddress(addr);
uint40 addrExpiration = LibMap.get(expiration, uint256(uint160(addr)));
uint40 timestamp = uint40(block.timestamp);
if (addrExpiration < timestamp) {
if (timestamp > maxExpiration) {
addrExpiration = type(uint40).max;
} else {
addrExpiration = timestamp + subLength;
}
} else if (addrExpiration < maxExpiration) {
// Unchecked is safe here as we know that expiration + subLength < MAX_UINT40
unchecked {
addrExpiration += subLength;
}
} else {
addrExpiration = type(uint40).max;
}
LibMap.set(expiration, uint256(uint160(addr)), addrExpiration);
emit SubscriptionPaid(addr, addrExpiration, subPrice);
unchecked {
++i;
}
}
}
if (referralAmount > 0) {
// We use `transfer` here to limit the amount of gas forwarded to the referrer
// As such, referrer addresses should be EOAs or contracts without fallback / receive functionality
payable(referrer).transfer(referralAmount);
emit ReferralPaid(referrer, referralAmount);
}
}
/**
* @notice Uses fees from subscriptions to buyback and burn CHART from the uniswap v2 pair
*
* @dev This is a high risk function, anyone with KEEPER_ROLE could potentially sandwich this call
* @dev Do not give KEEPER_ROLE to addresses unless you fully trust them
* @dev If you would like to ignore slippage, pass in 0 for amountOutMin
* @dev - Throws if `msg.sender` does not have owner or `KEEPER_ROLE`
* @dev - Throws if there is an error sending funds to `msg.sender`
*
* @dev On completion:
* @dev - `burnPercent`% of the balance of the contract is used to buy and burn CHART
* @dev - The remaining balance is sent to the `msg.sender` to cover operational expenses
* @dev - address(this).balance == 0
*
* @param amountOutMin Minimum amount of CHART to receive from `burnPercent`% * balance ETH
*/
function burnETH(uint256 amountOutMin) external {
_checkRolesOrOwner(KEEPER_ROLE);
uint256 balance = address(this).balance;
uint256 amountToBurn = balance * burnPercent / 100;
uint256 amountToSend = balance - amountToBurn;
address[] memory path = new address[](2);
path[0] = router.WETH();
path[1] = address(chart);
router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: amountToBurn}(
amountOutMin,
path,
address(0xdead),
block.timestamp
);
// Gated role, do not need to worry about gas to forward
(bool success,) = payable(msg.sender).call{value: amountToSend}("");
if (!success) {
revert SubscriptionManager__ErrorSendingKeeperFunds();
}
}
/**
* @notice Returns if an address has a current subscription
*
* @param user Address of user
* @return True if user has an active subscription, false if not
*/
function isAddressRegistered(address user) external view returns (bool) {
return block.timestamp <= LibMap.get(expiration, uint256(uint160(user)));
}
function getExpiration(address user) external view returns (uint40) {
return LibMap.get(expiration, uint256(uint160(user)));
}
/// @dev Convenience function to require user is owner
function _requireIsOwner() internal view {
if (msg.sender != owner()) {
revert SubscriptionManager__OnlyOwner();
}
}
/// @dev Convenience function to validate address input
function _requireValidAddress(address addr) internal pure {
if (addr == address(0)) {
revert SubscriptionManager__CannotRegisterAddressZero();
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {Ownable} from "./Ownable.sol";
/// @notice Simple single owner and multiroles authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
/// @dev While the ownable portion follows [EIP-173](https://eips.ethereum.org/EIPS/eip-173)
/// for compatibility, the nomenclature for the 2-step ownership handover and roles
/// may be unique to this codebase.
abstract contract OwnableRoles is Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The `user`'s roles is updated to `roles`.
/// Each bit of `roles` represents whether the role is set.
event RolesUpdated(address indexed user, uint256 indexed roles);
/// @dev `keccak256(bytes("RolesUpdated(address,uint256)"))`.
uint256 private constant _ROLES_UPDATED_EVENT_SIGNATURE =
0x715ad5ce61fc9595c7b415289d59cf203f23a94fa06f04af7e489a0a76e1fe26;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The role slot of `user` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _ROLE_SLOT_SEED))
/// let roleSlot := keccak256(0x00, 0x20)
/// ```
/// This automatically ignores the upper bits of the `user` in case
/// they are not clean, as well as keep the `keccak256` under 32-bytes.
///
/// Note: This is equal to `_OWNER_SLOT_NOT` in for gas efficiency.
uint256 private constant _ROLE_SLOT_SEED = 0x8b78c6d8;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Overwrite the roles directly without authorization guard.
function _setRoles(address user, uint256 roles) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
// Store the new value.
sstore(keccak256(0x0c, 0x20), roles)
// Emit the {RolesUpdated} event.
log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), roles)
}
}
/// @dev Updates the roles directly without authorization guard.
/// If `on` is true, each set bit of `roles` will be turned on,
/// otherwise, each set bit of `roles` will be turned off.
function _updateRoles(address user, uint256 roles, bool on) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
let roleSlot := keccak256(0x0c, 0x20)
// Load the current value.
let current := sload(roleSlot)
// Compute the updated roles if `on` is true.
let updated := or(current, roles)
// Compute the updated roles if `on` is false.
// Use `and` to compute the intersection of `current` and `roles`,
// `xor` it with `current` to flip the bits in the intersection.
if iszero(on) { updated := xor(current, and(current, roles)) }
// Then, store the new value.
sstore(roleSlot, updated)
// Emit the {RolesUpdated} event.
log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), updated)
}
}
/// @dev Grants the roles directly without authorization guard.
/// Each bit of `roles` represents the role to turn on.
function _grantRoles(address user, uint256 roles) internal virtual {
_updateRoles(user, roles, true);
}
/// @dev Removes the roles directly without authorization guard.
/// Each bit of `roles` represents the role to turn off.
function _removeRoles(address user, uint256 roles) internal virtual {
_updateRoles(user, roles, false);
}
/// @dev Throws if the sender does not have any of the `roles`.
function _checkRoles(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Throws if the sender is not the owner,
/// and does not have any of the `roles`.
/// Checks for ownership first, then lazily checks for roles.
function _checkOwnerOrRoles(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner.
// Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Throws if the sender does not have any of the `roles`,
/// and is not the owner.
/// Checks for roles first, then lazily checks for ownership.
function _checkRolesOrOwner(uint256 roles) internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, caller())
// Load the stored value, and if the `and` intersection
// of the value and `roles` is zero, revert.
if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
// If the caller is not the stored owner.
// Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Convenience function to return a `roles` bitmap from an array of `ordinals`.
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
/// Not recommended to be called on-chain.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _rolesFromOrdinals(uint8[] memory ordinals) internal pure returns (uint256 roles) {
/// @solidity memory-safe-assembly
assembly {
for { let i := shl(5, mload(ordinals)) } i { i := sub(i, 0x20) } {
// We don't need to mask the values of `ordinals`, as Solidity
// cleans dirty upper bits when storing variables into memory.
roles := or(shl(mload(add(ordinals, i)), 1), roles)
}
}
}
/// @dev Convenience function to return an array of `ordinals` from the `roles` bitmap.
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
/// Not recommended to be called on-chain.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ordinalsFromRoles(uint256 roles) internal pure returns (uint8[] memory ordinals) {
/// @solidity memory-safe-assembly
assembly {
// Grab the pointer to the free memory.
ordinals := mload(0x40)
let ptr := add(ordinals, 0x20)
let o := 0
// The absence of lookup tables, De Bruijn, etc., here is intentional for
// smaller bytecode, as this function is not meant to be called on-chain.
for { let t := roles } 1 {} {
mstore(ptr, o)
// `shr` 5 is equivalent to multiplying by 0x20.
// Push back into the ordinals array if the bit is set.
ptr := add(ptr, shl(5, and(t, 1)))
o := add(o, 1)
t := shr(o, roles)
if iszero(t) { break }
}
// Store the length of `ordinals`.
mstore(ordinals, shr(5, sub(ptr, add(ordinals, 0x20))))
// Allocate the memory.
mstore(0x40, ptr)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to grant `user` `roles`.
/// If the `user` already has a role, then it will be an no-op for the role.
function grantRoles(address user, uint256 roles) public payable virtual onlyOwner {
_grantRoles(user, roles);
}
/// @dev Allows the owner to remove `user` `roles`.
/// If the `user` does not have a role, then it will be an no-op for the role.
function revokeRoles(address user, uint256 roles) public payable virtual onlyOwner {
_removeRoles(user, roles);
}
/// @dev Allow the caller to remove their own roles.
/// If the caller does not have a role, then it will be an no-op for the role.
function renounceRoles(uint256 roles) public payable virtual {
_removeRoles(msg.sender, roles);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the roles of `user`.
function rolesOf(address user) public view virtual returns (uint256 roles) {
/// @solidity memory-safe-assembly
assembly {
// Compute the role slot.
mstore(0x0c, _ROLE_SLOT_SEED)
mstore(0x00, user)
// Load the stored value.
roles := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns whether `user` has any of `roles`.
function hasAnyRole(address user, uint256 roles) public view virtual returns (bool) {
return rolesOf(user) & roles != 0;
}
/// @dev Returns whether `user` has all of `roles`.
function hasAllRoles(address user, uint256 roles) public view virtual returns (bool) {
return rolesOf(user) & roles == roles;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by an account with `roles`.
modifier onlyRoles(uint256 roles) virtual {
_checkRoles(roles);
_;
}
/// @dev Marks a function as only callable by the owner or by an account
/// with `roles`. Checks for ownership first, then lazily checks for roles.
modifier onlyOwnerOrRoles(uint256 roles) virtual {
_checkOwnerOrRoles(roles);
_;
}
/// @dev Marks a function as only callable by an account with `roles`
/// or the owner. Checks for roles first, then lazily checks for ownership.
modifier onlyRolesOrOwner(uint256 roles) virtual {
_checkRolesOrOwner(roles);
_;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ROLE CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// IYKYK
uint256 internal constant _ROLE_0 = 1 << 0;
uint256 internal constant _ROLE_1 = 1 << 1;
uint256 internal constant _ROLE_2 = 1 << 2;
uint256 internal constant _ROLE_3 = 1 << 3;
uint256 internal constant _ROLE_4 = 1 << 4;
uint256 internal constant _ROLE_5 = 1 << 5;
uint256 internal constant _ROLE_6 = 1 << 6;
uint256 internal constant _ROLE_7 = 1 << 7;
uint256 internal constant _ROLE_8 = 1 << 8;
uint256 internal constant _ROLE_9 = 1 << 9;
uint256 internal constant _ROLE_10 = 1 << 10;
uint256 internal constant _ROLE_11 = 1 << 11;
uint256 internal constant _ROLE_12 = 1 << 12;
uint256 internal constant _ROLE_13 = 1 << 13;
uint256 internal constant _ROLE_14 = 1 << 14;
uint256 internal constant _ROLE_15 = 1 << 15;
uint256 internal constant _ROLE_16 = 1 << 16;
uint256 internal constant _ROLE_17 = 1 << 17;
uint256 internal constant _ROLE_18 = 1 << 18;
uint256 internal constant _ROLE_19 = 1 << 19;
uint256 internal constant _ROLE_20 = 1 << 20;
uint256 internal constant _ROLE_21 = 1 << 21;
uint256 internal constant _ROLE_22 = 1 << 22;
uint256 internal constant _ROLE_23 = 1 << 23;
uint256 internal constant _ROLE_24 = 1 << 24;
uint256 internal constant _ROLE_25 = 1 << 25;
uint256 internal constant _ROLE_26 = 1 << 26;
uint256 internal constant _ROLE_27 = 1 << 27;
uint256 internal constant _ROLE_28 = 1 << 28;
uint256 internal constant _ROLE_29 = 1 << 29;
uint256 internal constant _ROLE_30 = 1 << 30;
uint256 internal constant _ROLE_31 = 1 << 31;
uint256 internal constant _ROLE_32 = 1 << 32;
uint256 internal constant _ROLE_33 = 1 << 33;
uint256 internal constant _ROLE_34 = 1 << 34;
uint256 internal constant _ROLE_35 = 1 << 35;
uint256 internal constant _ROLE_36 = 1 << 36;
uint256 internal constant _ROLE_37 = 1 << 37;
uint256 internal constant _ROLE_38 = 1 << 38;
uint256 internal constant _ROLE_39 = 1 << 39;
uint256 internal constant _ROLE_40 = 1 << 40;
uint256 internal constant _ROLE_41 = 1 << 41;
uint256 internal constant _ROLE_42 = 1 << 42;
uint256 internal constant _ROLE_43 = 1 << 43;
uint256 internal constant _ROLE_44 = 1 << 44;
uint256 internal constant _ROLE_45 = 1 << 45;
uint256 internal constant _ROLE_46 = 1 << 46;
uint256 internal constant _ROLE_47 = 1 << 47;
uint256 internal constant _ROLE_48 = 1 << 48;
uint256 internal constant _ROLE_49 = 1 << 49;
uint256 internal constant _ROLE_50 = 1 << 50;
uint256 internal constant _ROLE_51 = 1 << 51;
uint256 internal constant _ROLE_52 = 1 << 52;
uint256 internal constant _ROLE_53 = 1 << 53;
uint256 internal constant _ROLE_54 = 1 << 54;
uint256 internal constant _ROLE_55 = 1 << 55;
uint256 internal constant _ROLE_56 = 1 << 56;
uint256 internal constant _ROLE_57 = 1 << 57;
uint256 internal constant _ROLE_58 = 1 << 58;
uint256 internal constant _ROLE_59 = 1 << 59;
uint256 internal constant _ROLE_60 = 1 << 60;
uint256 internal constant _ROLE_61 = 1 << 61;
uint256 internal constant _ROLE_62 = 1 << 62;
uint256 internal constant _ROLE_63 = 1 << 63;
uint256 internal constant _ROLE_64 = 1 << 64;
uint256 internal constant _ROLE_65 = 1 << 65;
uint256 internal constant _ROLE_66 = 1 << 66;
uint256 internal constant _ROLE_67 = 1 << 67;
uint256 internal constant _ROLE_68 = 1 << 68;
uint256 internal constant _ROLE_69 = 1 << 69;
uint256 internal constant _ROLE_70 = 1 << 70;
uint256 internal constant _ROLE_71 = 1 << 71;
uint256 internal constant _ROLE_72 = 1 << 72;
uint256 internal constant _ROLE_73 = 1 << 73;
uint256 internal constant _ROLE_74 = 1 << 74;
uint256 internal constant _ROLE_75 = 1 << 75;
uint256 internal constant _ROLE_76 = 1 << 76;
uint256 internal constant _ROLE_77 = 1 << 77;
uint256 internal constant _ROLE_78 = 1 << 78;
uint256 internal constant _ROLE_79 = 1 << 79;
uint256 internal constant _ROLE_80 = 1 << 80;
uint256 internal constant _ROLE_81 = 1 << 81;
uint256 internal constant _ROLE_82 = 1 << 82;
uint256 internal constant _ROLE_83 = 1 << 83;
uint256 internal constant _ROLE_84 = 1 << 84;
uint256 internal constant _ROLE_85 = 1 << 85;
uint256 internal constant _ROLE_86 = 1 << 86;
uint256 internal constant _ROLE_87 = 1 << 87;
uint256 internal constant _ROLE_88 = 1 << 88;
uint256 internal constant _ROLE_89 = 1 << 89;
uint256 internal constant _ROLE_90 = 1 << 90;
uint256 internal constant _ROLE_91 = 1 << 91;
uint256 internal constant _ROLE_92 = 1 << 92;
uint256 internal constant _ROLE_93 = 1 << 93;
uint256 internal constant _ROLE_94 = 1 << 94;
uint256 internal constant _ROLE_95 = 1 << 95;
uint256 internal constant _ROLE_96 = 1 << 96;
uint256 internal constant _ROLE_97 = 1 << 97;
uint256 internal constant _ROLE_98 = 1 << 98;
uint256 internal constant _ROLE_99 = 1 << 99;
uint256 internal constant _ROLE_100 = 1 << 100;
uint256 internal constant _ROLE_101 = 1 << 101;
uint256 internal constant _ROLE_102 = 1 << 102;
uint256 internal constant _ROLE_103 = 1 << 103;
uint256 internal constant _ROLE_104 = 1 << 104;
uint256 internal constant _ROLE_105 = 1 << 105;
uint256 internal constant _ROLE_106 = 1 << 106;
uint256 internal constant _ROLE_107 = 1 << 107;
uint256 internal constant _ROLE_108 = 1 << 108;
uint256 internal constant _ROLE_109 = 1 << 109;
uint256 internal constant _ROLE_110 = 1 << 110;
uint256 internal constant _ROLE_111 = 1 << 111;
uint256 internal constant _ROLE_112 = 1 << 112;
uint256 internal constant _ROLE_113 = 1 << 113;
uint256 internal constant _ROLE_114 = 1 << 114;
uint256 internal constant _ROLE_115 = 1 << 115;
uint256 internal constant _ROLE_116 = 1 << 116;
uint256 internal constant _ROLE_117 = 1 << 117;
uint256 internal constant _ROLE_118 = 1 << 118;
uint256 internal constant _ROLE_119 = 1 << 119;
uint256 internal constant _ROLE_120 = 1 << 120;
uint256 internal constant _ROLE_121 = 1 << 121;
uint256 internal constant _ROLE_122 = 1 << 122;
uint256 internal constant _ROLE_123 = 1 << 123;
uint256 internal constant _ROLE_124 = 1 << 124;
uint256 internal constant _ROLE_125 = 1 << 125;
uint256 internal constant _ROLE_126 = 1 << 126;
uint256 internal constant _ROLE_127 = 1 << 127;
uint256 internal constant _ROLE_128 = 1 << 128;
uint256 internal constant _ROLE_129 = 1 << 129;
uint256 internal constant _ROLE_130 = 1 << 130;
uint256 internal constant _ROLE_131 = 1 << 131;
uint256 internal constant _ROLE_132 = 1 << 132;
uint256 internal constant _ROLE_133 = 1 << 133;
uint256 internal constant _ROLE_134 = 1 << 134;
uint256 internal constant _ROLE_135 = 1 << 135;
uint256 internal constant _ROLE_136 = 1 << 136;
uint256 internal constant _ROLE_137 = 1 << 137;
uint256 internal constant _ROLE_138 = 1 << 138;
uint256 internal constant _ROLE_139 = 1 << 139;
uint256 internal constant _ROLE_140 = 1 << 140;
uint256 internal constant _ROLE_141 = 1 << 141;
uint256 internal constant _ROLE_142 = 1 << 142;
uint256 internal constant _ROLE_143 = 1 << 143;
uint256 internal constant _ROLE_144 = 1 << 144;
uint256 internal constant _ROLE_145 = 1 << 145;
uint256 internal constant _ROLE_146 = 1 << 146;
uint256 internal constant _ROLE_147 = 1 << 147;
uint256 internal constant _ROLE_148 = 1 << 148;
uint256 internal constant _ROLE_149 = 1 << 149;
uint256 internal constant _ROLE_150 = 1 << 150;
uint256 internal constant _ROLE_151 = 1 << 151;
uint256 internal constant _ROLE_152 = 1 << 152;
uint256 internal constant _ROLE_153 = 1 << 153;
uint256 internal constant _ROLE_154 = 1 << 154;
uint256 internal constant _ROLE_155 = 1 << 155;
uint256 internal constant _ROLE_156 = 1 << 156;
uint256 internal constant _ROLE_157 = 1 << 157;
uint256 internal constant _ROLE_158 = 1 << 158;
uint256 internal constant _ROLE_159 = 1 << 159;
uint256 internal constant _ROLE_160 = 1 << 160;
uint256 internal constant _ROLE_161 = 1 << 161;
uint256 internal constant _ROLE_162 = 1 << 162;
uint256 internal constant _ROLE_163 = 1 << 163;
uint256 internal constant _ROLE_164 = 1 << 164;
uint256 internal constant _ROLE_165 = 1 << 165;
uint256 internal constant _ROLE_166 = 1 << 166;
uint256 internal constant _ROLE_167 = 1 << 167;
uint256 internal constant _ROLE_168 = 1 << 168;
uint256 internal constant _ROLE_169 = 1 << 169;
uint256 internal constant _ROLE_170 = 1 << 170;
uint256 internal constant _ROLE_171 = 1 << 171;
uint256 internal constant _ROLE_172 = 1 << 172;
uint256 internal constant _ROLE_173 = 1 << 173;
uint256 internal constant _ROLE_174 = 1 << 174;
uint256 internal constant _ROLE_175 = 1 << 175;
uint256 internal constant _ROLE_176 = 1 << 176;
uint256 internal constant _ROLE_177 = 1 << 177;
uint256 internal constant _ROLE_178 = 1 << 178;
uint256 internal constant _ROLE_179 = 1 << 179;
uint256 internal constant _ROLE_180 = 1 << 180;
uint256 internal constant _ROLE_181 = 1 << 181;
uint256 internal constant _ROLE_182 = 1 << 182;
uint256 internal constant _ROLE_183 = 1 << 183;
uint256 internal constant _ROLE_184 = 1 << 184;
uint256 internal constant _ROLE_185 = 1 << 185;
uint256 internal constant _ROLE_186 = 1 << 186;
uint256 internal constant _ROLE_187 = 1 << 187;
uint256 internal constant _ROLE_188 = 1 << 188;
uint256 internal constant _ROLE_189 = 1 << 189;
uint256 internal constant _ROLE_190 = 1 << 190;
uint256 internal constant _ROLE_191 = 1 << 191;
uint256 internal constant _ROLE_192 = 1 << 192;
uint256 internal constant _ROLE_193 = 1 << 193;
uint256 internal constant _ROLE_194 = 1 << 194;
uint256 internal constant _ROLE_195 = 1 << 195;
uint256 internal constant _ROLE_196 = 1 << 196;
uint256 internal constant _ROLE_197 = 1 << 197;
uint256 internal constant _ROLE_198 = 1 << 198;
uint256 internal constant _ROLE_199 = 1 << 199;
uint256 internal constant _ROLE_200 = 1 << 200;
uint256 internal constant _ROLE_201 = 1 << 201;
uint256 internal constant _ROLE_202 = 1 << 202;
uint256 internal constant _ROLE_203 = 1 << 203;
uint256 internal constant _ROLE_204 = 1 << 204;
uint256 internal constant _ROLE_205 = 1 << 205;
uint256 internal constant _ROLE_206 = 1 << 206;
uint256 internal constant _ROLE_207 = 1 << 207;
uint256 internal constant _ROLE_208 = 1 << 208;
uint256 internal constant _ROLE_209 = 1 << 209;
uint256 internal constant _ROLE_210 = 1 << 210;
uint256 internal constant _ROLE_211 = 1 << 211;
uint256 internal constant _ROLE_212 = 1 << 212;
uint256 internal constant _ROLE_213 = 1 << 213;
uint256 internal constant _ROLE_214 = 1 << 214;
uint256 internal constant _ROLE_215 = 1 << 215;
uint256 internal constant _ROLE_216 = 1 << 216;
uint256 internal constant _ROLE_217 = 1 << 217;
uint256 internal constant _ROLE_218 = 1 << 218;
uint256 internal constant _ROLE_219 = 1 << 219;
uint256 internal constant _ROLE_220 = 1 << 220;
uint256 internal constant _ROLE_221 = 1 << 221;
uint256 internal constant _ROLE_222 = 1 << 222;
uint256 internal constant _ROLE_223 = 1 << 223;
uint256 internal constant _ROLE_224 = 1 << 224;
uint256 internal constant _ROLE_225 = 1 << 225;
uint256 internal constant _ROLE_226 = 1 << 226;
uint256 internal constant _ROLE_227 = 1 << 227;
uint256 internal constant _ROLE_228 = 1 << 228;
uint256 internal constant _ROLE_229 = 1 << 229;
uint256 internal constant _ROLE_230 = 1 << 230;
uint256 internal constant _ROLE_231 = 1 << 231;
uint256 internal constant _ROLE_232 = 1 << 232;
uint256 internal constant _ROLE_233 = 1 << 233;
uint256 internal constant _ROLE_234 = 1 << 234;
uint256 internal constant _ROLE_235 = 1 << 235;
uint256 internal constant _ROLE_236 = 1 << 236;
uint256 internal constant _ROLE_237 = 1 << 237;
uint256 internal constant _ROLE_238 = 1 << 238;
uint256 internal constant _ROLE_239 = 1 << 239;
uint256 internal constant _ROLE_240 = 1 << 240;
uint256 internal constant _ROLE_241 = 1 << 241;
uint256 internal constant _ROLE_242 = 1 << 242;
uint256 internal constant _ROLE_243 = 1 << 243;
uint256 internal constant _ROLE_244 = 1 << 244;
uint256 internal constant _ROLE_245 = 1 << 245;
uint256 internal constant _ROLE_246 = 1 << 246;
uint256 internal constant _ROLE_247 = 1 << 247;
uint256 internal constant _ROLE_248 = 1 << 248;
uint256 internal constant _ROLE_249 = 1 << 249;
uint256 internal constant _ROLE_250 = 1 << 250;
uint256 internal constant _ROLE_251 = 1 << 251;
uint256 internal constant _ROLE_252 = 1 << 252;
uint256 internal constant _ROLE_253 = 1 << 253;
uint256 internal constant _ROLE_254 = 1 << 254;
uint256 internal constant _ROLE_255 = 1 << 255;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for storage of packed unsigned integers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibMap.sol)
library LibMap {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STRUCTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev A uint8 map in storage.
struct Uint8Map {
mapping(uint256 => uint256) map;
}
/// @dev A uint16 map in storage.
struct Uint16Map {
mapping(uint256 => uint256) map;
}
/// @dev A uint32 map in storage.
struct Uint32Map {
mapping(uint256 => uint256) map;
}
/// @dev A uint40 map in storage. Useful for storing timestamps up to 34841 A.D.
struct Uint40Map {
mapping(uint256 => uint256) map;
}
/// @dev A uint64 map in storage.
struct Uint64Map {
mapping(uint256 => uint256) map;
}
/// @dev A uint128 map in storage.
struct Uint128Map {
mapping(uint256 => uint256) map;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the uint8 value at `index` in `map`.
function get(Uint8Map storage map, uint256 index) internal view returns (uint8 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(5, index))
result := byte(and(31, not(index)), sload(keccak256(0x00, 0x40)))
}
}
/// @dev Updates the uint8 value at `index` in `map`.
function set(Uint8Map storage map, uint256 index, uint8 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(5, index))
let s := keccak256(0x00, 0x40) // Storage slot.
mstore(0x00, sload(s))
mstore8(and(31, not(index)), value)
sstore(s, mload(0x00))
}
}
/// @dev Returns the uint16 value at `index` in `map`.
function get(Uint16Map storage map, uint256 index) internal view returns (uint16 result) {
result = uint16(map.map[index >> 4] >> ((index & 15) << 4));
}
/// @dev Updates the uint16 value at `index` in `map`.
function set(Uint16Map storage map, uint256 index, uint16 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(4, index))
let s := keccak256(0x00, 0x40) // Storage slot.
let o := shl(4, and(index, 15)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
let m := 0xffff // Value mask.
sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value)))))
}
}
/// @dev Returns the uint32 value at `index` in `map`.
function get(Uint32Map storage map, uint256 index) internal view returns (uint32 result) {
result = uint32(map.map[index >> 3] >> ((index & 7) << 5));
}
/// @dev Updates the uint32 value at `index` in `map`.
function set(Uint32Map storage map, uint256 index, uint32 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(3, index))
let s := keccak256(0x00, 0x40) // Storage slot.
let o := shl(5, and(index, 7)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
let m := 0xffffffff // Value mask.
sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value)))))
}
}
/// @dev Returns the uint40 value at `index` in `map`.
function get(Uint40Map storage map, uint256 index) internal view returns (uint40 result) {
unchecked {
result = uint40(map.map[index / 6] >> ((index % 6) * 40));
}
}
/// @dev Updates the uint40 value at `index` in `map`.
function set(Uint40Map storage map, uint256 index, uint40 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, div(index, 6))
let s := keccak256(0x00, 0x40) // Storage slot.
let o := mul(40, mod(index, 6)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
let m := 0xffffffffff // Value mask.
sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value)))))
}
}
/// @dev Returns the uint64 value at `index` in `map`.
function get(Uint64Map storage map, uint256 index) internal view returns (uint64 result) {
result = uint64(map.map[index >> 2] >> ((index & 3) << 6));
}
/// @dev Updates the uint64 value at `index` in `map`.
function set(Uint64Map storage map, uint256 index, uint64 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(2, index))
let s := keccak256(0x00, 0x40) // Storage slot.
let o := shl(6, and(index, 3)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
let m := 0xffffffffffffffff // Value mask.
sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value)))))
}
}
/// @dev Returns the uint128 value at `index` in `map`.
function get(Uint128Map storage map, uint256 index) internal view returns (uint128 result) {
result = uint128(map.map[index >> 1] >> ((index & 1) << 7));
}
/// @dev Updates the uint128 value at `index` in `map`.
function set(Uint128Map storage map, uint256 index, uint128 value) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, map.slot)
mstore(0x00, shr(1, index))
let s := keccak256(0x00, 0x40) // Storage slot.
let o := shl(7, and(index, 1)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
let m := 0xffffffffffffffffffffffffffffffff // Value mask.
sstore(s, xor(v, shl(o, and(m, xor(shr(o, v), value)))))
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import "@solady/tokens/ERC20.sol";
import "@solady/auth/Ownable.sol";
import "@openzeppelin/token/ERC20/IERC20.sol";
import "./IUniswapV2Factory.sol";
import "./IUniswapV2Router02.sol";
contract Nchart is ERC20, Ownable {
struct User {
bool isBlacklisted;
bool isAutomatedMarketMaker;
bool isExcludedFromFees;
bool isExcludedFromMaxTransactionAmount;
}
struct Fees {
uint8 buy;
uint8 sell;
uint8 liquidity;
uint8 revShare;
uint8 team;
}
struct Settings {
bool limitsInEffect;
bool swapEnabled;
bool blacklistRenounced;
bool feeChangeRenounced;
bool tradingActive;
/// @dev Upon enabling trading, record the end block for bot protection fee
/// @dev This fee is a 90% fee that is reduced by 5% every block for 18 blocks.
uint216 endBlock;
}
IUniswapV2Router02 public immutable uniswapV2Router;
address public immutable uniswapV2Pair;
/// @dev Constant to access the allowance slot
uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20;
uint256 public constant MAX_SUPPLY = 10_000_000 * 1e18;
uint256 public constant MIN_SWAP_AMOUNT = MAX_SUPPLY / 100_000; // 0.001%
uint256 public constant MAX_SWAP_AMOUNT = MAX_SUPPLY * 5 / 1_000; // 0.5%
uint256 public maxTransactionAmount;
uint256 public swapTokensAtAmount;
uint256 public maxWallet;
address public revShareWallet;
address public teamWallet;
bool private _swapping;
uint256 public tokensForBotProtection;
Fees public feeAmounts;
Settings private settings = Settings({
limitsInEffect: true,
swapEnabled: true,
blacklistRenounced: false,
feeChangeRenounced: false,
tradingActive: false,
endBlock: uint216(0)
});
mapping(address => User) private _users;
event ExcludeFromFees(address indexed account, bool isExcluded);
event ExcludeFromMaxTransaction(address indexed account, bool isExcluded);
event FailedSwapBackTransfer(address indexed destination, uint256 amount);
event FeesUpdated(uint8 buyFee, uint8 sellFee, uint8 revSharePercent, uint8 liquidityPercent, uint8 teamPercent);
event MaxTransactionAmountUpdated(uint256 newAmount, uint256 oldAmount);
event MaxWalletAmountUpdated(uint256 newAmount, uint256 oldAmount);
event RevShareWalletUpdated(address indexed newWallet, address indexed oldWallet);
event SetAutomatedMarketMakerPair(address indexed pair, bool value);
event SwapAndLiquify(uint256 tokensSwapped, uint256 ethReceived);
event SwapTokensAtAmountUpdated(uint256 newAmount, uint256 oldAmount);
event TeamWalletUpdated(address indexed newWallet, address indexed oldWallet);
event UpdateUniswapV2Router(address indexed newAddress, address indexed oldAddress);
error Nchart__BlacklistModificationDisabled();
error Nchart__BuyAmountGreaterThanMax();
error Nchart__CannotBlacklistLPPair();
error Nchart__CannotBlacklistRouter();
error Nchart__CannotRemovePairFromAMMs();
error Nchart__CannotSetWalletToAddressZero();
error Nchart__CannotTransferFromAddressZero();
error Nchart__CannotTransferToAddressZero();
error Nchart__ErrorWithdrawingEth();
error Nchart__FeeChangeRenounced();
error Nchart__MaxFeeFivePercent();
error Nchart__MaxTransactionTooLow();
error Nchart__MaxWalletAmountExceeded();
error Nchart__MaxWalletAmountTooLow();
error Nchart__OnlyOwner();
error Nchart__ReceiverBlacklisted();
error Nchart__ReceiverCannotBeAddressZero();
error Nchart__SellAmountGreaterThanMax();
error Nchart__SenderBlacklisted();
error Nchart__StuckEthWithdrawError();
error Nchart__SwapAmountGreaterThanMaximum();
error Nchart__SwapAmountLowerThanMinimum();
error Nchart__TokenAddressCannotBeAddressZero();
error Nchart__TradingNotActive();
constructor(address ownerWallet, address teamWallet_, address revShareWallet_, address routerAddress) {
_initializeOwner(ownerWallet);
IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(routerAddress);
uniswapV2Router = _uniswapV2Router;
uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());
maxTransactionAmount = MAX_SUPPLY / 100; // 1%
maxWallet = MAX_SUPPLY / 100; // 1%
swapTokensAtAmount = MAX_SUPPLY * 5 / 10_000; // 0.05%
feeAmounts = Fees({buy: 5, sell: 5, revShare: 0, liquidity: 25, team: 75});
revShareWallet = revShareWallet_;
teamWallet = teamWallet_;
_users[teamWallet_] = User({
isExcludedFromFees: true,
isExcludedFromMaxTransactionAmount: true,
isAutomatedMarketMaker: false,
isBlacklisted: false
});
_users[address(this)] = User({
isExcludedFromFees: true,
isExcludedFromMaxTransactionAmount: true,
isAutomatedMarketMaker: false,
isBlacklisted: false
});
_users[address(0xdead)] = User({
isExcludedFromFees: true,
isExcludedFromMaxTransactionAmount: true,
isAutomatedMarketMaker: false,
isBlacklisted: false
});
_users[address(ownerWallet)] = User({
isExcludedFromFees: true,
isExcludedFromMaxTransactionAmount: true,
isAutomatedMarketMaker: false,
isBlacklisted: false
});
_users[address(uniswapV2Router)] = User({
isExcludedFromMaxTransactionAmount: true,
isAutomatedMarketMaker: false,
isExcludedFromFees: false,
isBlacklisted: false
});
_users[address(uniswapV2Pair)] = User({
isExcludedFromMaxTransactionAmount: true,
isAutomatedMarketMaker: true,
isExcludedFromFees: false,
isBlacklisted: false
});
_mint(ownerWallet, MAX_SUPPLY);
}
receive() external payable {}
function name() public pure override returns (string memory) {
return "Nchart";
}
function symbol() public pure override returns (string memory) {
return "CHART";
}
function enableTrading() public {
_requireIsOwner();
settings.endBlock = uint216(block.number) + 19;
settings.tradingActive = true;
}
// remove limits after token is stable
function removeLimits() external {
_requireIsOwner();
settings.limitsInEffect = false;
}
// change the minimum amount of tokens to sell from fees
function updateSwapTokensAtAmount(uint256 newAmount) external {
_requireIsOwner();
if (newAmount < MIN_SWAP_AMOUNT) {
revert Nchart__SwapAmountLowerThanMinimum();
}
if (newAmount > MAX_SWAP_AMOUNT) {
revert Nchart__SwapAmountGreaterThanMaximum();
}
uint256 oldSwapAmount = swapTokensAtAmount;
swapTokensAtAmount = newAmount;
emit SwapTokensAtAmountUpdated(newAmount, oldSwapAmount);
}
function updateMaxTransactionAmount(uint256 newAmount) external {
_requireIsOwner();
if (newAmount < MAX_SUPPLY * 5 / 1000) {
revert Nchart__MaxTransactionTooLow();
}
uint256 oldMaxTransactionAmount = maxTransactionAmount;
maxTransactionAmount = newAmount;
emit MaxTransactionAmountUpdated(newAmount, oldMaxTransactionAmount);
}
function updateMaxWalletAmount(uint256 newNum) external {
_requireIsOwner();
if (newNum < MAX_SUPPLY / 100) {
revert Nchart__MaxWalletAmountTooLow();
}
uint256 oldMaxWallet = maxWallet;
maxWallet = newNum;
emit MaxWalletAmountUpdated(newNum, oldMaxWallet);
}
// only use to disable contract sales if absolutely necessary (emergency use only)
function updateSwapEnabled(bool enabled) external {
_requireIsOwner();
settings.swapEnabled = enabled;
}
function updateBuyFees(uint8 revShareFee, uint8 liquidityFee, uint8 teamFee) external {
_requireIsOwner();
if (settings.feeChangeRenounced) {
revert Nchart__FeeChangeRenounced();
}
uint8 totalFees = revShareFee + liquidityFee + teamFee;
if (totalFees > 5) {
revert Nchart__MaxFeeFivePercent();
}
uint8 sellFee = feeAmounts.sell;
uint8 revPercent = revShareFee * 100 / totalFees;
uint8 liqPercent = liquidityFee * 100 / totalFees;
uint8 teamPercent = 100 - revPercent - liqPercent;
feeAmounts =
Fees({buy: totalFees, sell: sellFee, revShare: revPercent, liquidity: liqPercent, team: teamPercent});
emit FeesUpdated(totalFees, sellFee, revPercent, liqPercent, teamPercent);
}
function updateSellFees(uint8 revShareFee, uint8 liquidityFee, uint8 teamFee) external {
_requireIsOwner();
if (settings.feeChangeRenounced) {
revert Nchart__FeeChangeRenounced();
}
uint8 totalFees = revShareFee + liquidityFee + teamFee;
if (totalFees > 5) {
revert Nchart__MaxFeeFivePercent();
}
uint8 buyFee = feeAmounts.buy;
uint8 revPercent = revShareFee * 100 / totalFees;
uint8 liqPercent = liquidityFee * 100 / totalFees;
uint8 teamPercent = 100 - revPercent - liqPercent;
feeAmounts =
Fees({buy: buyFee, sell: totalFees, revShare: revPercent, liquidity: liqPercent, team: teamPercent});
emit FeesUpdated(buyFee, totalFees, revPercent, liqPercent, teamPercent);
}
function excludeFromFees(address account, bool excluded) external {
_requireIsOwner();
_users[account].isExcludedFromFees = excluded;
emit ExcludeFromFees(account, excluded);
}
function excludeFromMaxTransaction(address account, bool isExcluded) external {
_requireIsOwner();
_users[account].isExcludedFromMaxTransactionAmount = isExcluded;
emit ExcludeFromMaxTransaction(account, isExcluded);
}
function setAutomatedMarketMakerPair(address pair, bool value) external {
_requireIsOwner();
if (pair == uniswapV2Pair) {
revert Nchart__CannotRemovePairFromAMMs();
}
_users[pair].isAutomatedMarketMaker = value;
emit SetAutomatedMarketMakerPair(pair, value);
}
function updateRevShareWallet(address newWallet) external {
_requireIsOwner();
if (newWallet == address(0)) {
revert Nchart__CannotSetWalletToAddressZero();
}
address oldWallet = revShareWallet;
revShareWallet = newWallet;
emit RevShareWalletUpdated(newWallet, oldWallet);
}
function updateTeamWallet(address newWallet) external {
_requireIsOwner();
if (newWallet == address(0)) {
revert Nchart__CannotSetWalletToAddressZero();
}
address oldWallet = teamWallet;
teamWallet = newWallet;
emit TeamWalletUpdated(newWallet, oldWallet);
}
function withdrawStuckChart(uint256 amount) external {
_requireIsOwner();
uint256 transferAmount;
if (amount == 0) {
transferAmount = balanceOf(address(this));
} else {
transferAmount = amount;
}
super._transfer(address(this), msg.sender, transferAmount);
}
function withdrawStuckToken(address _token) external {
_requireIsOwner();
if (_token == address(0)) {
revert Nchart__TokenAddressCannotBeAddressZero();
}
uint256 _contractBalance = IERC20(_token).balanceOf(address(this));
IERC20(_token).transfer(msg.sender, _contractBalance);
}
function withdrawStuckEth() external {
_requireIsOwner();
(bool success,) = msg.sender.call{value: address(this).balance}("");
if (!success) {
revert Nchart__ErrorWithdrawingEth();
}
}
function renounceBlacklist() external {
_requireIsOwner();
settings.blacklistRenounced = true;
}
function renounceFeeChange() external {
_requireIsOwner();
settings.feeChangeRenounced = true;
}
function blacklist(address account) external {
_requireIsOwner();
if (settings.blacklistRenounced) {
revert Nchart__BlacklistModificationDisabled();
}
if (account == uniswapV2Pair) {
revert Nchart__CannotBlacklistLPPair();
}
if (account == address(uniswapV2Router)) {
revert Nchart__CannotBlacklistRouter();
}
_users[account].isBlacklisted = true;
}
// @dev unblacklist address; not affected by blacklistRenounced incase team wants to unblacklist v3 pools down the
// @dev road
function unblacklist(address account) external {
_requireIsOwner();
_users[account].isBlacklisted = false;
}
function isExcludedFromFees(address account) external view returns (bool) {
return _users[account].isExcludedFromFees;
}
function isExcludedFromMaxTransactionAmount(address account) external view returns (bool) {
return _users[account].isExcludedFromMaxTransactionAmount;
}
function isAutomatedMarketMakerPair(address pair) external view returns (bool) {
return _users[pair].isAutomatedMarketMaker;
}
function isBlacklisted(address account) external view returns (bool) {
return _users[account].isBlacklisted;
}
function isSwapEnabled() external view returns (bool) {
return settings.swapEnabled;
}
function isBlacklistRenounced() external view returns (bool) {
return settings.blacklistRenounced;
}
function isFeeChangeRenounced() external view returns (bool) {
return settings.feeChangeRenounced;
}
function isTradingActive() external view returns (bool) {
return settings.tradingActive;
}
function isLimitInEffect() external view returns (bool) {
return settings.limitsInEffect;
}
function transfer(address to, uint256 amount) public override returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
// Check allowance and reduce it if used, reverts with `InsufficientAllowance()` if not approved.
assembly {
let from_ := shl(96, from)
// Compute the allowance slot and load its value.
mstore(0x20, caller())
mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if iszero(eq(allowance_, not(0))) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
}
_transfer(from, to, amount);
return true;
}
function _transfer(address from, address to, uint256 amount) internal override {
// Ignore mints, burns not enabled
if (from == address(0)) {
revert Nchart__CannotTransferFromAddressZero();
}
if (to == address(0)) {
revert Nchart__CannotTransferToAddressZero();
}
User memory fromData = _users[from];
User memory toData = _users[to];
Settings memory settingCache = settings;
if (!settingCache.tradingActive) {
if (!fromData.isExcludedFromFees) {
if (!toData.isExcludedFromFees) {
revert Nchart__TradingNotActive();
}
}
}
// Apply blacklist protection
if (fromData.isBlacklisted) {
revert Nchart__SenderBlacklisted();
}
if (toData.isBlacklisted) {
revert Nchart__ReceiverBlacklisted();
}
// If zero amount, continue
if (amount == 0) {
return;
}
bool excludedFromFees = fromData.isExcludedFromFees || toData.isExcludedFromFees;
// Cache transaction type for reference.
// 1 = Buy
// 2 = Sell
// 3 = Transfer
uint8 txType = 3;
if (fromData.isAutomatedMarketMaker) {
// Buys originate from the AMM pair
txType = 1;
} else if (toData.isAutomatedMarketMaker) {
// Sells send funds to AMM pair
txType = 2;
}
if (!_swapping) {
if (settingCache.limitsInEffect) {
//when buy
if (txType == 1 && !toData.isExcludedFromMaxTransactionAmount) {
if (amount > maxTransactionAmount) {
revert Nchart__BuyAmountGreaterThanMax();
}
if (amount + balanceOf(to) > maxWallet) {
revert Nchart__MaxWalletAmountExceeded();
}
}
//when sell
else if (txType == 2 && !fromData.isExcludedFromMaxTransactionAmount) {
if (amount > maxTransactionAmount) {
revert Nchart__SellAmountGreaterThanMax();
}
} else if (!toData.isExcludedFromMaxTransactionAmount) {
if (amount + balanceOf(to) > maxWallet) {
revert Nchart__MaxWalletAmountExceeded();
}
}
}
if (settingCache.swapEnabled) {
// Only sells will trigger the fee swap
if (txType == 2) {
if (balanceOf(address(this)) >= swapTokensAtAmount) {
_swapping = true;
_swapBack();
_swapping = false;
}
}
}
}
if (txType < 3) {
bool takeFee = !_swapping;
// if any account belongs to _isExcludedFromFee account then remove the fee
if (excludedFromFees) {
takeFee = false;
}
uint256 fees = 0;
// only take fees on buys/sells, do not take on wallet transfers
if (takeFee) {
Fees memory feeCache = feeAmounts;
// on sell
if (txType == 2) {
if (feeCache.sell > 0) {
fees = amount * feeCache.sell / 100;
}
}
// on buy
else if (txType == 1) {
if (feeCache.buy > 0) {
fees = amount * feeCache.buy / 100;
}
}
if (block.number < settingCache.endBlock) {
uint256 blocksLeft = settingCache.endBlock - block.number;
uint256 botFeeMultiplier = 90;
// Apply sniper protection - first 18 blocks have a fee reduced 5% each block.
if (blocksLeft < 18) {
botFeeMultiplier -= (5 * (18 - blocksLeft));
}
uint256 botFee = (amount * botFeeMultiplier) / 100;
super._transfer(from, teamWallet, botFee);
amount -= botFee;
tokensForBotProtection += botFee;
}
amount -= fees;
if (fees > 0) {
super._transfer(from, address(this), fees);
}
}
}
super._transfer(from, to, amount);
}
function _swapTokensForEth(uint256 tokenAmount) internal {
// generate the uniswap pair path of token -> weth
address[] memory path = new address[](2);
path[0] = address(this);
path[1] = uniswapV2Router.WETH();
_approve(address(this), address(uniswapV2Router), tokenAmount);
// make the swap
uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
tokenAmount,
0, // accept any amount of ETH
path,
address(this),
block.timestamp
);
}
function _addLiquidity(uint256 tokenAmount, uint256 ethAmount) internal {
// approve token transfer to cover all possible scenarios
_approve(address(this), address(uniswapV2Router), tokenAmount);
// add the liquidity
uniswapV2Router.addLiquidityETH{value: ethAmount}(
address(this),
tokenAmount,
0, // slippage is unavoidable
0, // slippage is unavoidable
owner(),
block.timestamp
);
}
function _swapBack() internal {
// Cache values
uint256 contractBalance = balanceOf(address(this));
Fees memory feeCache = feeAmounts;
bool success;
if (contractBalance == 0) {
return;
}
// Prevent too many tokens from being swapped
uint256 maxAmount = swapTokensAtAmount * 20;
if (contractBalance > maxAmount) {
contractBalance = maxAmount;
}
uint256 liquidityAmount = contractBalance * feeCache.liquidity / 100;
// Halve the amount of liquidity tokens
uint256 liquidityTokens = liquidityAmount - (liquidityAmount / 2);
uint256 amountToSwapForETH = contractBalance - liquidityTokens;
uint256 initialETHBalance = address(this).balance;
_swapTokensForEth(amountToSwapForETH);
uint256 ethBalance = address(this).balance - initialETHBalance;
uint256 ethForRevShare = ethBalance * feeCache.revShare / 100;
uint256 ethForTeam = ethBalance * feeCache.team / 100;
uint256 ethForLiquidity = ethBalance - ethForRevShare - ethForTeam;
if (liquidityTokens > 0 && ethForLiquidity > 0) {
_addLiquidity(liquidityTokens, ethForLiquidity);
emit SwapAndLiquify(amountToSwapForETH, ethForLiquidity);
}
address teamWallet_ = teamWallet;
(success,) = address(teamWallet_).call{value: ethForTeam}("");
if (!success) {
emit FailedSwapBackTransfer(teamWallet_, ethForTeam);
}
if (ethForRevShare > 0) {
(success,) = address(revShareWallet).call{value: ethForRevShare}("");
if (!success) {
emit FailedSwapBackTransfer(revShareWallet, ethForRevShare);
}
}
}
function _requireIsOwner() internal view {
if (msg.sender != owner()) {
revert Nchart__OnlyOwner();
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;
import "./IUniswapV2Router01.sol";
interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
/// @dev While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by: `not(_OWNER_SLOT_NOT)`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
uint256 private constant _OWNER_SLOT_NOT = 0x8b78c6d8;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(not(_OWNER_SLOT_NOT), newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := not(_OWNER_SLOT_NOT)
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(not(_OWNER_SLOT_NOT)))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(not(_OWNER_SLOT_NOT))
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC20 + EIP-2612 implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
///
/// @dev Note:
/// The ERC20 standard allows minting and transferring to and from the zero address,
/// minting and transferring zero tokens, as well as self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
abstract contract ERC20 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The total supply has overflowed.
error TotalSupplyOverflow();
/// @dev The allowance has overflowed.
error AllowanceOverflow();
/// @dev The allowance has underflowed.
error AllowanceUnderflow();
/// @dev Insufficient balance.
error InsufficientBalance();
/// @dev Insufficient allowance.
error InsufficientAllowance();
/// @dev The permit is invalid.
error InvalidPermit();
/// @dev The permit has expired.
error PermitExpired();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 amount);
/// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
event Approval(address indexed owner, address indexed spender, uint256 amount);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The storage slot for the total supply.
uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c;
/// @dev The balance slot of `owner` is given by:
/// ```
/// mstore(0x0c, _BALANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let balanceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2;
/// @dev The allowance slot of (`owner`, `spender`) is given by:
/// ```
/// mstore(0x20, spender)
/// mstore(0x0c, _ALLOWANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let allowanceSlot := keccak256(0x0c, 0x34)
/// ```
uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20;
/// @dev The nonce slot of `owner` is given by:
/// ```
/// mstore(0x0c, _NONCES_SLOT_SEED)
/// mstore(0x00, owner)
/// let nonceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _NONCES_SLOT_SEED = 0x38377508;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 METADATA */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the name of the token.
function name() public view virtual returns (string memory);
/// @dev Returns the symbol of the token.
function symbol() public view virtual returns (string memory);
/// @dev Returns the decimals places of the token.
function decimals() public view virtual returns (uint8) {
return 18;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the amount of tokens in existence.
function totalSupply() public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_TOTAL_SUPPLY_SLOT)
}
}
/// @dev Returns the amount of tokens owned by `owner`.
function balanceOf(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
function allowance(address owner, address spender)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x34))
}
}
/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
///
/// Emits a {Approval} event.
function approve(address spender, uint256 amount) public virtual returns (bool) {
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
}
return true;
}
/// @dev Atomically increases the allowance granted to `spender` by the caller.
///
/// Emits a {Approval} event.
function increaseAllowance(address spender, uint256 difference) public virtual returns (bool) {
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and load its value.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, caller())
let allowanceSlot := keccak256(0x0c, 0x34)
let allowanceBefore := sload(allowanceSlot)
// Add to the allowance.
let allowanceAfter := add(allowanceBefore, difference)
// Revert upon overflow.
if lt(allowanceAfter, allowanceBefore) {
mstore(0x00, 0xf9067066) // `AllowanceOverflow()`.
revert(0x1c, 0x04)
}
// Store the updated allowance.
sstore(allowanceSlot, allowanceAfter)
// Emit the {Approval} event.
mstore(0x00, allowanceAfter)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
}
return true;
}
/// @dev Atomically decreases the allowance granted to `spender` by the caller.
///
/// Emits a {Approval} event.
function decreaseAllowance(address spender, uint256 difference) public virtual returns (bool) {
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and load its value.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, caller())
let allowanceSlot := keccak256(0x0c, 0x34)
let allowanceBefore := sload(allowanceSlot)
// Revert if will underflow.
if lt(allowanceBefore, difference) {
mstore(0x00, 0x8301ab38) // `AllowanceUnderflow()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
let allowanceAfter := sub(allowanceBefore, difference)
sstore(allowanceSlot, allowanceAfter)
// Emit the {Approval} event.
mstore(0x00, allowanceAfter)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
}
return true;
}
/// @dev Transfer `amount` tokens from the caller to `to`.
///
/// Requirements:
/// - `from` must at least have `amount`.
///
/// Emits a {Transfer} event.
function transfer(address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(msg.sender, to, amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, caller())
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c)))
}
_afterTokenTransfer(msg.sender, to, amount);
return true;
}
/// @dev Transfers `amount` tokens from `from` to `to`.
///
/// Note: Does not update the allowance if it is the maximum uint256 value.
///
/// Requirements:
/// - `from` must at least have `amount`.
/// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(from, to, amount);
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// Compute the allowance slot and load its value.
mstore(0x20, caller())
mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if iszero(eq(allowance_, not(0))) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
_afterTokenTransfer(from, to, amount);
return true;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-2612 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the current nonce for `owner`.
/// This value is used to compute the signature for EIP-2612 permit.
function nonces(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// Compute the nonce slot and load its value.
mstore(0x0c, _NONCES_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`,
/// authorized by a signed approval by `owner`.
///
/// Emits a {Approval} event.
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
bytes32 domainSeparator = DOMAIN_SEPARATOR();
/// @solidity memory-safe-assembly
assembly {
// Grab the free memory pointer.
let m := mload(0x40)
// Revert if the block timestamp greater than `deadline`.
if gt(timestamp(), deadline) {
mstore(0x00, 0x1a15a3cc) // `PermitExpired()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
owner := shr(96, shl(96, owner))
spender := shr(96, shl(96, spender))
// Compute the nonce slot and load its value.
mstore(0x0c, _NONCES_SLOT_SEED)
mstore(0x00, owner)
let nonceSlot := keccak256(0x0c, 0x20)
let nonceValue := sload(nonceSlot)
// Increment and store the updated nonce.
sstore(nonceSlot, add(nonceValue, 1))
// Prepare the inner hash.
// `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
// forgefmt: disable-next-item
mstore(m, 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9)
mstore(add(m, 0x20), owner)
mstore(add(m, 0x40), spender)
mstore(add(m, 0x60), value)
mstore(add(m, 0x80), nonceValue)
mstore(add(m, 0xa0), deadline)
// Prepare the outer hash.
mstore(0, 0x1901)
mstore(0x20, domainSeparator)
mstore(0x40, keccak256(m, 0xc0))
// Prepare the ecrecover calldata.
mstore(0, keccak256(0x1e, 0x42))
mstore(0x20, and(0xff, v))
mstore(0x40, r)
mstore(0x60, s)
pop(staticcall(gas(), 1, 0, 0x80, 0x20, 0x20))
// If the ecrecover fails, the returndatasize will be 0x00,
// `owner` will be be checked if it equals the hash at 0x00,
// which evaluates to false (i.e. 0), and we will revert.
// If the ecrecover succeeds, the returndatasize will be 0x20,
// `owner` will be compared against the returned address at 0x20.
if iszero(eq(mload(returndatasize()), owner)) {
mstore(0x00, 0xddafbaef) // `InvalidPermit()`.
revert(0x1c, 0x04)
}
// Compute the allowance slot and store the value.
// The `owner` is already at slot 0x20.
mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender))
sstore(keccak256(0x2c, 0x34), value)
// Emit the {Approval} event.
log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}
/// @dev Returns the EIP-2612 domains separator.
function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40) // Grab the free memory pointer.
}
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
bytes32 nameHash = keccak256(bytes(name()));
/// @solidity memory-safe-assembly
assembly {
let m := result
// `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
// forgefmt: disable-next-item
mstore(m, 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f)
mstore(add(m, 0x20), nameHash)
// `keccak256("1")`.
// forgefmt: disable-next-item
mstore(add(m, 0x40), 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
result := keccak256(m, 0xa0)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL MINT FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Mints `amount` tokens to `to`, increasing the total supply.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 amount) internal virtual {
_beforeTokenTransfer(address(0), to, amount);
/// @solidity memory-safe-assembly
assembly {
let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT)
let totalSupplyAfter := add(totalSupplyBefore, amount)
// Revert if the total supply overflows.
if lt(totalSupplyAfter, totalSupplyBefore) {
mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`.
revert(0x1c, 0x04)
}
// Store the updated total supply.
sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter)
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c)))
}
_afterTokenTransfer(address(0), to, amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL BURN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Burns `amount` tokens from `from`, reducing the total supply.
///
/// Emits a {Transfer} event.
function _burn(address from, uint256 amount) internal virtual {
_beforeTokenTransfer(from, address(0), amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, from)
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Subtract and store the updated total supply.
sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount))
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0)
}
_afterTokenTransfer(from, address(0), amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL TRANSFER FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Moves `amount` of tokens from `from` to `to`.
function _transfer(address from, address to, uint256 amount) internal virtual {
_beforeTokenTransfer(from, to, amount);
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
_afterTokenTransfer(from, to, amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL ALLOWANCE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Updates the allowance of `owner` for `spender` based on spent `amount`.
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and load its value.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if iszero(eq(allowance_, not(0))) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
}
}
/// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`.
///
/// Emits a {Approval} event.
function _approve(address owner, address spender, uint256 amount) internal virtual {
/// @solidity memory-safe-assembly
assembly {
let owner_ := shl(96, owner)
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED))
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c)))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HOOKS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Hook that is called before any transfer of tokens.
/// This includes minting and burning.
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/// @dev Hook that is called after any transfer of tokens.
/// This includes minting and burning.
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;
interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint256);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint256) external view returns (address pair);
function allPairsLength() external view returns (uint256);
function createPair(address tokenA, address tokenB) external returns (address pair);
function setFeeTo(address) external;
function setFeeToSetter(address) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;
interface IUniswapV2Router01 {
function factory() external returns (address);
function WETH() external returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB);
function removeLiquidityETH(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountToken, uint256 amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountA, uint256 amountB);
function removeLiquidityETHWithPermit(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountToken, uint256 amountETH);
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactETHForTokens(uint256 amountOutMin, address[] calldata path, address to, uint256 deadline)
external
payable
returns (uint256[] memory amounts);
function swapTokensForExactETH(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapETHForExactTokens(uint256 amountOut, address[] calldata path, address to, uint256 deadline)
external
payable
returns (uint256[] memory amounts);
function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) external pure returns (uint256 amountB);
function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
external
pure
returns (uint256 amountOut);
function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut)
external
pure
returns (uint256 amountIn);
function getAmountsOut(uint256 amountIn, address[] calldata path)
external
view
returns (uint256[] memory amounts);
function getAmountsIn(uint256 amountOut, address[] calldata path)
external
view
returns (uint256[] memory amounts);
}