Contract Name:
StableFeeMiddleware
Contract Source Code:
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.14;
import { AggregatorV3Interface } from "chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import { ReentrancyGuard } from "openzeppelin-contracts/contracts/security/ReentrancyGuard.sol";
import { ICyberIdMiddleware } from "../../interfaces/ICyberIdMiddleware.sol";
import { ITokenReceiver } from "../../interfaces/ITokenReceiver.sol";
import { DataTypes } from "../../libraries/DataTypes.sol";
import { LowerCaseCyberIdMiddleware } from "./base/LowerCaseCyberIdMiddleware.sol";
contract StableFeeMiddleware is LowerCaseCyberIdMiddleware, ReentrancyGuard {
/*//////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
/**
* @notice ETH/USD Oracle address.
*/
AggregatorV3Interface public immutable usdOracle;
/**
* @notice TokenReceiver contract address.
*/
ITokenReceiver public immutable tokenReceiver;
/**
* If true, the middleware will charge the fee to token receiver.
*/
bool public rebateEnabled;
/**
* @notice The address that receives the fee.
*/
address public recipient;
/**
* @notice The price of each letter in USD.
*/
uint256 public price1Letter;
uint256 public price2Letter;
uint256 public price3Letter;
uint256 public price4Letter;
uint256 public price5Letter;
uint256 public price6Letter;
uint256 public price7To11Letter;
uint256 public price12AndMoreLetter;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event RebateChanged(bool rebateEnabled);
event RecipientChanged(address recipient);
event StableFeeChanged(
uint256 price1Letter,
uint256 price2Letter,
uint256 price3Letter,
uint256 price4Letter,
uint256 price5Letter,
uint256 price6Letter,
uint256 price7To11Letter,
uint256 price12AndMoreLetter
);
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
address _oracleAddress,
address _tokenReceiver,
address cyberId
) LowerCaseCyberIdMiddleware(cyberId) {
usdOracle = AggregatorV3Interface(_oracleAddress);
tokenReceiver = ITokenReceiver(_tokenReceiver);
}
/*//////////////////////////////////////////////////////////////
ICyberIdMiddleware OVERRIDES
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ICyberIdMiddleware
function setMwData(bytes calldata data) external override onlyNameRegistry {
(
bool _rebateEnabled,
address _recipient,
uint256[8] memory prices
) = abi.decode(data, (bool, address, uint256[8]));
rebateEnabled = _rebateEnabled;
recipient = _recipient;
price1Letter = prices[0];
price2Letter = prices[1];
price3Letter = prices[2];
price4Letter = prices[3];
price5Letter = prices[4];
price6Letter = prices[5];
price7To11Letter = prices[6];
price12AndMoreLetter = prices[7];
emit RebateChanged(_rebateEnabled);
emit RecipientChanged(_recipient);
emit StableFeeChanged(
prices[0],
prices[1],
prices[2],
prices[3],
prices[4],
prices[5],
prices[6],
prices[7]
);
}
/// @inheritdoc ICyberIdMiddleware
function preRegister(
DataTypes.RegisterCyberIdParams calldata params,
bytes calldata
)
external
payable
override
onlyNameRegistry
nonReentrant
returns (uint256)
{
uint256 cost = getPriceWei(params.cid);
_chargeAndRefundOverPayment(cost, params.to, params.msgSender);
return cost;
}
/// @inheritdoc ICyberIdMiddleware
function skipCommit() external pure virtual override returns (bool) {
return true;
}
/*//////////////////////////////////////////////////////////////
PUBLIC VIEW
//////////////////////////////////////////////////////////////*/
function getPriceWei(string calldata cid) public view returns (uint256) {
return _attoUSDToWei(_getUSDPrice(cid));
}
/*//////////////////////////////////////////////////////////////
INTERNAL LOGIC
//////////////////////////////////////////////////////////////*/
function _getUSDPrice(string calldata cid) internal view returns (uint256) {
// LowerCaseCyberIdMiddleware ensures that each cid character only occupies 1 byte
uint256 len = bytes(cid).length;
uint256 usdPrice;
if (len >= 12) {
usdPrice = price12AndMoreLetter;
} else if (len >= 7) {
usdPrice = price7To11Letter;
} else if (len == 6) {
usdPrice = price6Letter;
} else if (len == 5) {
usdPrice = price5Letter;
} else if (len == 4) {
usdPrice = price4Letter;
} else if (len == 3) {
usdPrice = price3Letter;
} else if (len == 2) {
usdPrice = price2Letter;
} else {
usdPrice = price1Letter;
}
return usdPrice;
}
function _getPrice() internal view returns (int256) {
// prettier-ignore
(
uint80 roundID,
int price,
/* uint startedAt */,
uint updatedAt,
/*uint80 answeredInRound*/
) = usdOracle.latestRoundData();
require(roundID != 0, "INVALID_ORACLE_ROUND_ID");
require(price > 0, "INVALID_ORACLE_PRICE");
require(updatedAt > block.timestamp - 3 hours, "STALE_ORACLE_PRICE");
return price;
}
function _attoUSDToWei(uint256 amount) internal view returns (uint256) {
uint256 ethPrice = uint256(_getPrice());
return (amount * 1e8) / ethPrice;
}
function _chargeAndRefundOverPayment(
uint256 cost,
address depositTo,
address refundTo
) internal {
require(msg.value >= cost, "INSUFFICIENT_FUNDS");
/**
* Already checked msg.value >= cost
*/
uint256 overpayment;
unchecked {
overpayment = msg.value - cost;
}
if (overpayment > 0) {
(bool refundSuccess, ) = refundTo.call{ value: overpayment }("");
require(refundSuccess, "REFUND_FAILED");
}
if (rebateEnabled) {
tokenReceiver.depositTo{ value: cost }(depositTo);
} else {
(bool chargeSuccess, ) = recipient.call{ value: cost }("");
require(chargeSuccess, "CHARGE_FAILED");
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.14;
import { DataTypes } from "../libraries/DataTypes.sol";
interface ICyberIdMiddleware {
/**
* @notice Sets data for middleware.
*
* @param data Extra data to set.
*/
function setMwData(bytes calldata data) external;
/**
* @notice Indicates if commit-reveal should be skipped.
*/
function skipCommit() external pure returns (bool);
/**
* @notice Process that runs before the register happens.
*
* @param params The params for register cid.
* @param data Extra data to process.
*/
function preRegister(
DataTypes.RegisterCyberIdParams calldata params,
bytes calldata data
) external payable returns (uint256);
/**
* @notice Validates the name pattern.
*
* @param name The name to validate.
*/
function namePatternValid(
string calldata name
) external view returns (bool);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.14;
/**
* @title TokenReceiver
* @author CyberConnect
* @notice A contract that receive native token and record the amount.
* The deposit only record the cumulative amount and withdraw won't affect
* the deposit value.
*/
interface ITokenReceiver {
function depositTo(address to) external payable;
function withdraw(address to, uint256 amount) external;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.14;
library DataTypes {
struct EIP712Signature {
uint8 v;
bytes32 r;
bytes32 s;
uint256 deadline;
}
struct MetadataPair {
string key;
string value;
}
struct RegisterNameParams {
address msgSender;
string name;
bytes32 parentNode;
address to;
}
struct RegisterCyberIdParams {
address msgSender;
string cid;
address to;
}
struct BatchRegisterCyberIdParams {
string cid;
address to;
address resolver;
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.14;
import { ICyberIdMiddleware } from "../../../interfaces/ICyberIdMiddleware.sol";
abstract contract LowerCaseCyberIdMiddleware is ICyberIdMiddleware {
/*//////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
address public immutable NAME_REGISTRY; // solhint-disable-line
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address nameRegistry) {
NAME_REGISTRY = nameRegistry;
}
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
/**
* @dev Reverts if called by any account other than the name registry.
*/
modifier onlyNameRegistry() {
require(NAME_REGISTRY == msg.sender, "NOT_NAME_REGISTRY");
_;
}
/*//////////////////////////////////////////////////////////////
ICyberIdMiddleware OVERRIDES
//////////////////////////////////////////////////////////////*/
/// @inheritdoc ICyberIdMiddleware
function skipCommit() external pure virtual override returns (bool) {
return false;
}
/// @inheritdoc ICyberIdMiddleware
function namePatternValid(
string calldata name
) external pure virtual override returns (bool) {
bytes memory byteName = bytes(name);
if (byteName.length > 20 || byteName.length < 1) {
return false;
}
uint256 byteNameLength = byteName.length;
for (uint256 i = 0; i < byteNameLength; ) {
bytes1 b = byteName[i];
if ((b >= "0" && b <= "9") || (b >= "a" && b <= "z") || b == "_") {
unchecked {
++i;
}
} else {
return false;
}
}
return true;
}
}