Latest 25 from a total of 117 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Claim | 26566350 | 108 days ago | IN | 0 FRAX | 0.00006087 | ||||
| Claim | 26256599 | 115 days ago | IN | 0 FRAX | 0.00009044 | ||||
| Claim | 26226743 | 115 days ago | IN | 0 FRAX | 0.00003847 | ||||
| Claim | 26149969 | 117 days ago | IN | 0 FRAX | 0.00001574 | ||||
| Claim | 26130956 | 118 days ago | IN | 0 FRAX | 0.00001467 | ||||
| Claim | 26105601 | 118 days ago | IN | 0 FRAX | 0.00001106 | ||||
| Claim | 26045359 | 120 days ago | IN | 0 FRAX | 0.00010921 | ||||
| Claim | 26045347 | 120 days ago | IN | 0 FRAX | 0.00011029 | ||||
| Claim | 26045334 | 120 days ago | IN | 0 FRAX | 0.00011434 | ||||
| Claim | 26043476 | 120 days ago | IN | 0 FRAX | 0.0000909 | ||||
| Claim | 26043469 | 120 days ago | IN | 0 FRAX | 0.0000953 | ||||
| Claim | 26043335 | 120 days ago | IN | 0 FRAX | 0.00007572 | ||||
| Claim | 26043325 | 120 days ago | IN | 0 FRAX | 0.00007266 | ||||
| Claim | 26043308 | 120 days ago | IN | 0 FRAX | 0.00007883 | ||||
| Claim | 26043299 | 120 days ago | IN | 0 FRAX | 0.00007011 | ||||
| Claim | 26043243 | 120 days ago | IN | 0 FRAX | 0.00007522 | ||||
| Claim | 26043139 | 120 days ago | IN | 0 FRAX | 0.00008277 | ||||
| Claim | 26035834 | 120 days ago | IN | 0 FRAX | 0.00004086 | ||||
| Claim | 26035745 | 120 days ago | IN | 0 FRAX | 0.00003694 | ||||
| Claim | 26035734 | 120 days ago | IN | 0 FRAX | 0.00003806 | ||||
| Claim | 26035663 | 120 days ago | IN | 0 FRAX | 0.00004117 | ||||
| Claim | 26034500 | 120 days ago | IN | 0 FRAX | 0.00004786 | ||||
| Claim | 26034483 | 120 days ago | IN | 0 FRAX | 0.00004814 | ||||
| Claim | 26032351 | 120 days ago | IN | 0 FRAX | 0.00003458 | ||||
| Claim | 26031041 | 120 days ago | IN | 0 FRAX | 0.0000392 |
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 25339094 | 136 days ago | Contract Creation | 0 FRAX |
Cross-Chain Transactions
Loading...
Loading
Minimal Proxy Contract for 0xbe852f3b38f65d5e251a3127c25dc21fa9a9129e
Contract Name:
EchoCampaign
Compiler Version
v0.8.30+commit.73712a01
Optimization Enabled:
Yes with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
// ███████╗ ██████╗██╗ ██╗ ██████╗ ███╗ ███╗ █████╗ ██████╗ ██╗ ██╗███████╗████████╗
// ██╔════╝██╔════╝██║ ██║██╔═══██╗ ████╗ ████║██╔══██╗██╔══██╗██║ ██╔╝██╔════╝╚══██╔══╝
// █████╗ ██║ ███████║██║ ██║ ██╔████╔██║███████║██████╔╝█████╔╝ █████╗ ██║
// ██╔══╝ ██║ ██╔══██║██║ ██║ ██║╚██╔╝██║██╔══██║██╔══██╗██╔═██╗ ██╔══╝ ██║
// ███████╗╚██████╗██║ ██║╚██████╔╝ ██║ ╚═╝ ██║██║ ██║██║ ██║██║ ██╗███████╗ ██║
// ╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝
// ================================= EchoCampaign V2 =================================
// =================================== Summer 2025 ===================================
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IEchoAdministration} from "../../interfaces/IEchoAdministration.sol";
import {IEchoCampaignFactory} from "../../interfaces/IEchoCampaignFactory.sol";
import {IEchoContents} from "../../interfaces/IEchoContents.sol";
import {IEchoCampaign, QualificationData, BudgetInfo, SocialKPIs, QA_METHOD, Status, Finalizer, Token, PostInfo, BasicInfo, CampaignInitData, KPI} from "../../interfaces/IEchoCampaign.sol";
import {Clone} from "../../libs/Clone.sol";
import {UD60x18, ud, unwrap} from "@prb/math/src/UD60x18.sol";
/// @title Echo Campaign contract
/// @author Dynabits.org
contract EchoCampaign is IEchoCampaign, Clone {
/********************************\
|-*-*-*-*-* STATES *-*-*-*-*-|
\********************************/
Status private _status;
bool private no_mid_refunding;
bool private _campaignPaused;
uint232 private _totalPosts;
uint256 private _totalEffectiveKPIs;
uint256 private _totalEligibleContents;
uint256 private _totalPaidAmount;
string private _ipfsCID;
BudgetInfo private _budgetInfo;
SocialKPIs private _socialKPIs;
QualificationData[] private _qaData;
mapping(string => bool) public registeredActionKind;
mapping(string => uint256) public contentEffectiveKPI;
/*******************************\
|-*-*-*-* CONSTANTS *-*-*-*-|
\*******************************/
address private immutable FRAX_USD;
// FOR PRODUCTION
// 0xFc00000000000000000000000000000000000001;
// FOR TESTNET
// 0xa35Bd0843e49F4eCF7Fb716FDADdc86f4482dB8E;
// FOR LOCAL TEST
// 0xecE1B93b2dA75C6EE3b78D158416df18Ae9FEE15;
/********************************\
|-*-*-*-*-* EVENTS *-*-*-*-*-|
\********************************/
event CampaignPauseToggled(bool indexed paused, address campaign);
event BudgetIncreased(
uint256 indexed addedAmount,
uint256 indexed lastBudget,
uint256 indexed newBudget,
address donator,
address campaign
);
event CampaignFinalized(
address indexed finalizer,
Finalizer indexed finalizerType,
uint256 indexed refundedAmount,
uint256 newBudget,
address campaign
);
event Claimed(
address indexed contentCreator,
uint256 indexed rewardedAmount,
uint256 indexed refundAmount,
uint256 totalPaidAmount,
string contentLink,
address campaign
);
event CampaignFinished(uint256 indexed finishedAt, address campaign);
/********************************\
|-*-*-*-*-* ERRORS *-*-*-*-*-|
\********************************/
error ONLY_FACTORY();
error ONLY_CONTENTS();
error ONLY_PROTOCOL_ADMIN();
error ONLY_OWNER();
error ONLY_FINALIZER_ORACLE();
error CAMPAIGN_PAUSED();
error INVALID_CONTENT_LINK();
error NO_CONTENT_AUTHOR();
error DUPLICATE_QA(string qa);
error DUPLICATE_KPI(string kpi);
error ONLY_IN_PROGRESS_STATUS(Status status);
error ONLY_UPCOMING_OR_IN_PROGRESS_STATUS(Status status);
error ONLY_FINISHED_CAMPAIGN_STATUS();
error ONLY_CLAIMABLE_STATUS();
error NOT_ELIGIBLE_FOR_WITHDRAW();
error CHECK_QAS_OR_KPIS();
error GIVEN_QA_KIND_DOESNT_EXIST_ON_CAMPAIGN(string qaKind);
error GIVEN_KPI_KIND_DOESNT_EXIST_ON_CAMPAIGN(string kpiKind);
/*******************************\
|-*-*-*-* MODIFIERS *-*-*-*-|
\*******************************/
modifier onlyContents() {
if (msg.sender != contents()) revert ONLY_CONTENTS();
_;
}
modifier onlyProtocolAdmin() {
if (msg.sender != IEchoAdministration(admin()).protocolAdmin())
revert ONLY_PROTOCOL_ADMIN();
_;
}
modifier onlyOwner() {
if (msg.sender != owner()) revert ONLY_OWNER();
_;
}
modifier onlyFinalizerOracle() {
(, , , address finalizerOracle) = IEchoAdministration(admin())
.modelData(
address(1),
keccak256(abi.encodePacked("CONFIG: Finalizer"))
);
if (msg.sender != finalizerOracle) revert ONLY_FINALIZER_ORACLE();
_;
}
modifier onlyInProgressStatus() {
if (currentStatus() != Status.inProgress)
revert ONLY_IN_PROGRESS_STATUS(currentStatus());
_;
}
/******************************\
|-*-*-*-* BUILT-IN *-*-*-*-|
\******************************/
constructor() {
if (block.chainid == 252)
FRAX_USD = 0xFc00000000000000000000000000000000000001;
else if (block.chainid == 2522)
FRAX_USD = 0xa35Bd0843e49F4eCF7Fb716FDADdc86f4482dB8E;
else FRAX_USD = 0xecE1B93b2dA75C6EE3b78D158416df18Ae9FEE15;
}
/// @inheritdoc IEchoCampaign
function init(
string calldata ipfsCID,
BudgetInfo calldata budgetInfo_,
SocialKPIs calldata socialKPIs_,
QualificationData[] calldata qaData_
) external {
// Ensure only the factory contract can initialize.
if (msg.sender != factory()) revert ONLY_FACTORY();
// Store campaign metadata.
_ipfsCID = ipfsCID;
_budgetInfo = budgetInfo_;
_socialKPIs = socialKPIs_;
uint256 i;
string memory action;
// Register Qualification Actions.
while (i < qaData_.length) {
_qaData.push(qaData_[i]);
// Determine the QA method prefix.
if (qaData_[i].method == QA_METHOD.AI) action = "QA-AI: ";
else if (qaData_[i].method == QA_METHOD.COMMUNITY)
action = "QA-C: ";
else action = "QA-V: ";
// Revert the operation if a duplicate qualification is detected.
if (registeredActionKind[string.concat(action, qaData_[i].kind)])
revert DUPLICATE_QA(string.concat(action, qaData_[i].kind));
// Register the Qualification.
registeredActionKind[string.concat(action, qaData_[i].kind)] = true;
unchecked {
i++;
}
}
// Register Social KPI Actions.
delete i;
action = string.concat(socialKPIs_.social, ": ");
while (i < socialKPIs_.kpis.length) {
// Revert the operation if a duplicate KPI is detected.
if (
registeredActionKind[
string.concat(action, socialKPIs_.kpis[i].method)
]
)
revert DUPLICATE_KPI(
string.concat(action, socialKPIs_.kpis[i].method)
);
// Register the SocialKPI.
registeredActionKind[
string.concat(action, socialKPIs_.kpis[i].method)
] = true;
unchecked {
i++;
}
}
}
/********************************\
|-*-*-* ADMINISTRATION *-*-*-|
\********************************/
/// @inheritdoc IEchoCampaign
function togglePauseCampaign() external onlyProtocolAdmin {
// Toggle the paused state of the campaign.
_campaignPaused = !_campaignPaused;
// Emit an event to notify the state change.
emit CampaignPauseToggled(_campaignPaused, address(this));
}
/// @inheritdoc IEchoCampaign
function finalizeInProgressCampaign()
external
onlyProtocolAdmin
onlyInProgressStatus
{
// Calculate and refund the possible remaining budget.
uint256 refundedAmount = _refundPossibleAmountLeft();
// Emit event indicating campaign finalization.
emit CampaignFinalized(
msg.sender,
Finalizer.protocolAdmin,
refundedAmount,
_budgetInfo.reservedAmount,
address(this)
);
}
/// @notice Recover ERC20 tokens
/// @param tokenAddress address of ERC20 token
/// @param tokenAmount amount to be withdrawn
function recoverERC20(address tokenAddress, uint256 tokenAmount)
external
onlyProtocolAdmin
{
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = tokenAddress.call(
abi.encodeWithSelector(0xa9059cbb, msg.sender, tokenAmount)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"TransferHelper: TRANSFER_FAILED"
);
}
/*******************************\
|*-*-*-* EXTERNALS *-*-*-*-*|
\*******************************/
/// @inheritdoc IEchoCampaign
function increaseBudget(uint256 amount) external {
// Ensure the campaign is in an `upcoming` or `inProgress` status.
if (
currentStatus() != Status.upcoming &&
currentStatus() != Status.inProgress
) revert ONLY_UPCOMING_OR_IN_PROGRESS_STATUS(currentStatus());
// Transfer the funds from sender to contract.
IERC20(_budgetInfo.token).transferFrom(
msg.sender,
address(this),
amount
);
// Update the reserved budget amount.
unchecked {
_budgetInfo.reservedAmount += uint128(amount);
}
// Emit event notifying budget increase.
emit BudgetIncreased(
amount,
_budgetInfo.reservedAmount - amount,
_budgetInfo.reservedAmount,
msg.sender,
address(this)
);
}
/// @inheritdoc IEchoCampaign
function applyContent(string calldata contentLink)
external
onlyInProgressStatus
{
// Ensure the content link is not empty.
if (bytes(contentLink).length == 0) revert INVALID_CONTENT_LINK();
// Calculate the application fee.
uint256 totalFee = applicationFee();
// Transfer the application fee to protocol admin.
if (totalFee != 0)
IERC20(FRAX_USD).transferFrom(
msg.sender,
IEchoAdministration(admin()).protocolAdmin(),
totalFee
);
// Increment the total posts count.
_totalPosts++;
// Register the content link in the EchoContents contract.
IEchoContents(contents()).addContentLink(
contentLink,
_budgetInfo.maxPerPost
);
}
/// @inheritdoc IEchoCampaign
function finalizeFinishedCampaign() external onlyFinalizerOracle {
// Ensure campaign is in the 'finished' status.
if (currentStatus() != Status.finished)
revert ONLY_FINISHED_CAMPAIGN_STATUS();
// Calculate and refund the remaining budget.
uint256 refundedAmount = _refundPossibleAmountLeft();
// Emit event notifying campaign finalization.
emit CampaignFinalized(
msg.sender,
Finalizer.finalizerOracle,
refundedAmount,
_budgetInfo.reservedAmount,
address(this)
);
}
/// @inheritdoc IEchoCampaign
function updateKPIs(
string calldata contentLink,
uint256 additionalContentEffectiveKPI,
bool additional
)
external
onlyContents
returns (
uint256 totalEffectiveKPIs,
uint256 contentEffectiveKPI_,
uint256 totalEligibleContents,
int8 eligibilityPolarity
)
{
// If this is the first KPI update for the content, increase the eligible content count.
if (contentEffectiveKPI[contentLink] == 0) {
_totalEligibleContents++;
eligibilityPolarity = 1;
}
// Modify KPI based on the 'additional' flag.
if (additional) {
_totalEffectiveKPIs += additionalContentEffectiveKPI;
contentEffectiveKPI[contentLink] += additionalContentEffectiveKPI;
} else {
_totalEffectiveKPIs -= additionalContentEffectiveKPI;
contentEffectiveKPI[contentLink] -= additionalContentEffectiveKPI;
if (contentEffectiveKPI[contentLink] == 0) {
_totalEligibleContents--;
eligibilityPolarity--;
}
}
// Return updated KPI values.
totalEffectiveKPIs = _totalEffectiveKPIs;
contentEffectiveKPI_ = contentEffectiveKPI[contentLink];
totalEligibleContents = _totalEligibleContents;
}
/// @inheritdoc IEchoCampaign
function claim(string calldata contentLink) external {
// Ensure the campaign is in a claimable state.
if (_status != Status.claimable) revert ONLY_CLAIMABLE_STATUS();
// Retrieve content registration details and author's action kind.
(bool contentRegistered, , , uint256 actionKindRes) = IEchoContents(
contents()
).contentToCampaign(address(this), contentLink, "CONFIG: AuthorWallet");
// Revert if the content is not registered under this campaign.
if (!contentRegistered) revert INVALID_CONTENT_LINK();
// Ensure the content has an associated author registered by the oracle.
if (actionKindRes == 0) revert NO_CONTENT_AUTHOR();
// Check if the content has an effective KPI score and is eligible for rewards.
if (contentEffectiveKPI[contentLink] == 0)
revert NOT_ELIGIBLE_FOR_WITHDRAW();
// Calculate the reward amount based on the content's KPI ratio.
uint256 contentKPIratio = (contentEffectiveKPI[contentLink] * 1e6) /
_totalEffectiveKPIs;
uint256 rewardAmount = (_budgetInfo.reservedAmount * contentKPIratio) /
1e6;
uint256 refundAmount;
// Adjust reserved budget and determine refund amount if applicable.
if (_budgetInfo.maxPerPost == 0) {
if (_totalEligibleContents == 1)
rewardAmount = _budgetInfo.reservedAmount - _totalPaidAmount;
} else {
if (rewardAmount >= _budgetInfo.maxPerPost)
rewardAmount = _budgetInfo.maxPerPost;
if (_totalEligibleContents != 1) {
if (!no_mid_refunding && rewardAmount < _budgetInfo.maxPerPost)
refundAmount = _budgetInfo.maxPerPost - rewardAmount;
} else {
if (
rewardAmount < _budgetInfo.reservedAmount - _totalPaidAmount
)
refundAmount =
(_budgetInfo.reservedAmount - _totalPaidAmount) -
rewardAmount;
}
// Refund excess funds if applicable.
if (refundAmount != 0) {
IERC20(_budgetInfo.token).transfer(
refundAddress(),
refundAmount
);
}
}
// Transfer the calculated reward to the content's registered author.
IERC20(_budgetInfo.token).transfer(
address(uint160(actionKindRes)),
rewardAmount
);
// Remove the content's KPI score tracking and decrement the eligible content counter.
delete contentEffectiveKPI[contentLink];
_totalEligibleContents--;
_totalPaidAmount += rewardAmount + refundAmount;
// Emit an event to log the claim transaction.
emit Claimed(
address(uint160(actionKindRes)),
rewardAmount,
refundAmount,
_totalPaidAmount,
contentLink,
address(this)
);
// If all eligible claims have been processed, finalize the campaign.
if (_totalEligibleContents == 0) {
_status = Status.finalized;
delete _budgetInfo.reservedAmount;
emit CampaignFinished(block.timestamp, address(this));
}
}
/*****************************\
|-*-*-*-* GETTERS *-*-*-*-|
\*****************************/
/// @inheritdoc IEchoCampaign
function budgetInfo() external view returns (BudgetInfo memory) {
return _budgetInfo;
}
/// @inheritdoc IEchoCampaign
function socialKPIs() external view returns (SocialKPIs memory) {
return _socialKPIs;
}
/// @inheritdoc IEchoCampaign
function qaData() external view returns (QualificationData[] memory) {
return _qaData;
}
/// @inheritdoc IEchoCampaign
function rewardEstimation(
QualificationData[] calldata qas,
KPI[] calldata kpis
) external view returns (uint256) {
// Validate input arrays to ensure they are non-empty
// and match expected campaign `_qaData` & `_socialKPIs` lengths.
if (
qas.length == 0 ||
kpis.length == 0 ||
qas.length != _qaData.length ||
kpis.length != _socialKPIs.kpis.length
) revert CHECK_QAS_OR_KPIS();
uint256 i;
UD60x18 contentQaScore = ud(1e18);
string memory action;
// Determine the QA method prefix for action identification.
if (qas[i].method == QA_METHOD.AI) action = "QA-AI: ";
else if (qas[i].method == QA_METHOD.COMMUNITY) action = "QA-C: ";
else action = "QA-V: ";
// Calculate the content QA score based on provided qualifications.
while (i < qas.length) {
if (qas[i].pct == 0) break;
// Ensure the QA kind exists within the campaign.
if (!registeredActionKind[string.concat(action, qas[i].kind)])
revert GIVEN_QA_KIND_DOESNT_EXIST_ON_CAMPAIGN(
string.concat(action, qas[i].kind)
);
// Note: There is no explicit check for duplicate QA evaluations.
contentQaScore = contentQaScore.mul(
ud(qas[i].pct * 1e13).pow(ud(_qaData[i].pct * 1e16))
);
unchecked {
i++;
}
}
// If the loop did not process all QAs, return zero as a no reward case.
if (i != qas.length) return 0;
delete i;
action = string.concat(_socialKPIs.social, ": ");
UD60x18 contentKPIscore = ud(0);
// Calculate the content KPI score based on provided KPIs.
while (i < kpis.length) {
// Ensure the KPI kind exists within the campaign.
if (!registeredActionKind[string.concat(action, kpis[i].method)])
revert GIVEN_KPI_KIND_DOESNT_EXIST_ON_CAMPAIGN(
string.concat(action, kpis[i].method)
);
// Note: There is no explicit check to ensure KPIs are in the correct order.
// It is assumed they are provided in the correct order.
// Ensure that the provided KPI meets the required minimum threshold.
// If the submitted KPI value is lower than the predefined minimum,
// set `contentKPIscore` to zero and exit the loop as a no reward case.
if (_socialKPIs.kpis[i].min > kpis[i].min) {
contentKPIscore = ud(0);
break;
}
// Accumulate the KPI score based on predefined percentages and the KPI ratio.
contentKPIscore = contentKPIscore.add(
ud(uint256(_socialKPIs.kpis[i].pct) * 1e16).mul(
ud(kpis[i].min).div(ud(_socialKPIs.kpis[i].ratio))
)
);
unchecked {
i++;
}
}
if (i != kpis.length) return 0;
// Compute the final effective KPI score.
uint256 effectiveKPI = (contentQaScore.mul(contentKPIscore)).unwrap();
// Calculate the content KPI ratio relative to the total effective KPIs.
uint256 contentKPIratio = (effectiveKPI * 1e6) /
(_totalEffectiveKPIs + effectiveKPI);
// Determine the reward amount based on the reserved campaign budget.
uint256 rewardAmount = (_budgetInfo.reservedAmount * contentKPIratio) /
1e6;
// Ensure the reward does not exceed the maximum allowed per post.
if (
_budgetInfo.maxPerPost != 0 &&
rewardAmount >= _budgetInfo.maxPerPost
) {
rewardAmount = _budgetInfo.maxPerPost;
}
return rewardAmount;
}
/// @inheritdoc IEchoCampaign
function details()
external
view
returns (
BasicInfo memory basicInfo_,
CampaignInitData memory initData_,
BudgetInfo memory budgetInfo_,
Token memory token_,
SocialKPIs memory socialKPIs_,
QualificationData[] memory qaData_,
PostInfo memory postInfo_,
Status status_
)
{
basicInfo_ = BasicInfo(
name(),
_ipfsCID,
owner(),
startTime(),
endTime()
);
initData_ = IEchoCampaignFactory(factory()).campaignNameToInitData(
name()
);
budgetInfo_ = _budgetInfo;
token_ = Token(
IERC20Metadata(_budgetInfo.token).name(),
IERC20Metadata(_budgetInfo.token).symbol(),
IERC20Metadata(_budgetInfo.token).decimals()
);
socialKPIs_ = _socialKPIs;
qaData_ = _qaData;
(uint256 lastPostTime, uint256 lastUpdatedTime) = IEchoContents(
contents()
).campaignContentInfo(address(this));
postInfo_ = PostInfo(
_totalPosts,
_totalEligibleContents,
_totalEffectiveKPIs,
lastPostTime,
lastUpdatedTime,
applicationFee()
);
status_ = currentStatus();
}
/// @inheritdoc IEchoCampaign
function getContentCreator(string calldata contentLink)
external
view
returns (address)
{
(bool contentRegistered, , , uint256 contentCreator) = IEchoContents(
contents()
).contentToCampaign(address(this), contentLink, "CONFIG: AuthorWallet");
if (!contentRegistered) revert INVALID_CONTENT_LINK();
return address(uint160(contentCreator));
}
/// @inheritdoc IEchoCampaign
function currentStatus() public view returns (Status) {
// If the status has been explicitly set to claimable or finalized, return it directly.
if (_status == Status.claimable || _status == Status.finalized)
return _status;
// If the current time is before the campaign start, mark it as upcoming.
if (block.timestamp <= startTime()) return Status.upcoming;
// If the campaign is active (between start and end time) and not finalized.
if (block.timestamp > startTime() && block.timestamp <= endTime())
if (_campaignPaused) return Status.paused;
else return Status.inProgress;
// If the current time is past the campaign end, mark it as finished.
return Status.finished;
}
/// @inheritdoc IEchoCampaign
function applicationFee() public view returns (uint256) {
// Get the total number of qualification data entries.
uint256 length = _qaData.length;
uint256 i;
uint256 totalFee;
uint256 tempFee;
string memory action;
// Iterate through each qualification data entry to compute its respective oracle fee.
while (i < length) {
// Determine the action type based on the QA method.
if (_qaData[i].method == QA_METHOD.AI) action = "QA-AI: ";
else if (_qaData[i].method == QA_METHOD.COMMUNITY)
action = "QA-C: ";
else action = "QA-V: ";
// Fetch the fee associated with the given QA kind from the administration contract.
(, , tempFee, ) = IEchoAdministration(admin()).modelData(
_qaData[i].oracle,
keccak256(abi.encodePacked(action, _qaData[i].kind))
);
if (tempFee == 0 || tempFee == 1) delete tempFee;
unchecked {
// Accumulate the fee.
totalFee += tempFee;
i++;
}
}
// Reset iteration variables for the next calculation.
delete i;
length = _socialKPIs.kpis.length;
action = string.concat(_socialKPIs.social, ": ");
// Iterate through each social KPI to compute its respective fee.
while (i < length) {
// Fetch the fee associated with the KPI from the administration contract.
(, , tempFee, ) = IEchoAdministration(admin()).modelData(
address(0),
keccak256(abi.encodePacked(action, _socialKPIs.kpis[i].method))
);
if (tempFee == 0 || tempFee == 1) delete tempFee;
unchecked {
// Accumulate the fee.
totalFee += tempFee;
i++;
}
}
// Return the total accumulated fee for both QA data and social KPIs.
return totalFee;
}
/// @inheritdoc IEchoCampaign
function name() public view returns (string memory) {
return
IEchoCampaignFactory(factory()).campaignNameHashToName(nameHash());
}
/// @inheritdoc IEchoCampaign
function contents() public view returns (address) {
return IEchoCampaignFactory(factory()).ECHO_CONTENTS();
}
/// @inheritdoc IEchoCampaign
function admin() public pure returns (address) {
return _getArgAddress(0);
}
/// @inheritdoc IEchoCampaign
function factory() public pure returns (address) {
return _getArgAddress(20);
}
/// @inheritdoc IEchoCampaign
function owner() public pure returns (address) {
return _getArgAddress(40);
}
/// @inheritdoc IEchoCampaign
function refundAddress() public pure returns (address) {
return _getArgAddress(60);
}
/// @inheritdoc IEchoCampaign
function nameHash() public pure returns (bytes32) {
return bytes32(_getArgUint256(80));
}
/// @inheritdoc IEchoCampaign
function startTime() public pure returns (uint256) {
return _getArgUint256(112);
}
/// @inheritdoc IEchoCampaign
function endTime() public pure returns (uint256) {
return _getArgUint256(144);
}
/*****************************\
|-*-*-*-* PRIVATE *-*-*-*-|
\*****************************/
function _refundPossibleAmountLeft() private returns (uint128) {
uint128 refundAmount;
_status = Status.claimable;
if (
_budgetInfo.maxPerPost != 0 &&
_totalEligibleContents * _budgetInfo.maxPerPost <
_budgetInfo.reservedAmount
) {
refundAmount = uint128(
_budgetInfo.reservedAmount -
(_totalEligibleContents * _budgetInfo.maxPerPost)
);
IERC20(_budgetInfo.token).transfer(refundAddress(), refundAmount);
_budgetInfo.reservedAmount -= refundAmount;
return refundAmount;
}
if (_budgetInfo.maxPerPost == 0 && _totalEligibleContents == 0) {
IERC20(_budgetInfo.token).transfer(
owner(),
_budgetInfo.reservedAmount
);
delete _budgetInfo.reservedAmount;
return refundAmount;
}
no_mid_refunding = true;
return refundAmount;
}
}// SPDX-License-Identifier: MIT pragma solidity >=0.8.19; /* ██████╗ ██████╗ ██████╗ ███╗ ███╗ █████╗ ████████╗██╗ ██╗ ██╔══██╗██╔══██╗██╔══██╗████╗ ████║██╔══██╗╚══██╔══╝██║ ██║ ██████╔╝██████╔╝██████╔╝██╔████╔██║███████║ ██║ ███████║ ██╔═══╝ ██╔══██╗██╔══██╗██║╚██╔╝██║██╔══██║ ██║ ██╔══██║ ██║ ██║ ██║██████╔╝██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ██╗ ██╗██████╗ ██████╗ ██████╗ ██╗ ██╗ ██╗ █████╗ ██║ ██║██╔══██╗██╔════╝ ██╔═████╗╚██╗██╔╝███║██╔══██╗ ██║ ██║██║ ██║███████╗ ██║██╔██║ ╚███╔╝ ╚██║╚█████╔╝ ██║ ██║██║ ██║██╔═══██╗████╔╝██║ ██╔██╗ ██║██╔══██╗ ╚██████╔╝██████╔╝╚██████╔╝╚██████╔╝██╔╝ ██╗ ██║╚█████╔╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚════╝ */ import "./ud60x18/Casting.sol"; import "./ud60x18/Constants.sol"; import "./ud60x18/Conversions.sol"; import "./ud60x18/Errors.sol"; import "./ud60x18/Helpers.sol"; import "./ud60x18/Math.sol"; import "./ud60x18/ValueType.sol";
// SPDX-License-Identifier: BSD
pragma solidity 0.8.30;
/// @title Clone
/// @author zefram.eth
/// @notice Provides helper functions for reading immutable args from calldata
contract Clone {
/// @notice Reads an immutable arg with type address
/// @param argOffset The offset of the arg in the packed data
/// @return arg The arg value
function _getArgAddress(
uint256 argOffset
) internal pure returns (address arg) {
uint256 offset = _getImmutableArgsOffset();
// solhint-disable-next-line no-inline-assembly
assembly {
arg := shr(0x60, calldataload(add(offset, argOffset)))
}
}
/// @notice Reads an immutable arg with type uint256
/// @param argOffset The offset of the arg in the packed data
/// @return arg The arg value
function _getArgUint256(
uint256 argOffset
) internal pure returns (uint256 arg) {
uint256 offset = _getImmutableArgsOffset();
// solhint-disable-next-line no-inline-assembly
assembly {
arg := calldataload(add(offset, argOffset))
}
}
/// @notice Reads a uint256 array stored in the immutable args.
/// @param argOffset The offset of the arg in the packed data
/// @param arrLen Number of elements in the array
/// @return arr The array
function _getArgUint256Array(
uint256 argOffset,
uint64 arrLen
) internal pure returns (uint256[] memory arr) {
uint256 offset = _getImmutableArgsOffset();
uint256 el;
arr = new uint256[](arrLen);
for (uint64 i = 0; i < arrLen; i++) {
// solhint-disable-next-line no-inline-assembly
assembly {
el := calldataload(add(add(offset, argOffset), mul(i, 32)))
}
arr[i] = el;
}
return arr;
}
/// @notice Reads an immutable arg with type uint64
/// @param argOffset The offset of the arg in the packed data
/// @return arg The arg value
function _getArgUint64(
uint256 argOffset
) internal pure returns (uint64 arg) {
uint256 offset = _getImmutableArgsOffset();
// solhint-disable-next-line no-inline-assembly
assembly {
arg := shr(0xc0, calldataload(add(offset, argOffset)))
}
}
/// @notice Reads an immutable arg with type uint8
/// @param argOffset The offset of the arg in the packed data
/// @return arg The arg value
function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) {
uint256 offset = _getImmutableArgsOffset();
// solhint-disable-next-line no-inline-assembly
assembly {
arg := shr(0xf8, calldataload(add(offset, argOffset)))
}
}
/// @return offset The offset of the packed immutable args in calldata
function _getImmutableArgsOffset() internal pure returns (uint256 offset) {
// solhint-disable-next-line no-inline-assembly
assembly {
offset := sub(
calldatasize(),
add(shr(240, calldataload(sub(calldatasize(), 2))), 2)
)
}
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
/***************************\
|*-*-*-* TYPES *-*-*-*|
\***************************/
enum QA_METHOD {
AI,
COMMUNITY,
VOTE
}
enum Status {
upcoming,
inProgress,
paused,
finished,
claimable,
finalized
}
enum Finalizer {
protocolAdmin,
finalizerOracle
}
struct CampaignInitData {
uint64 initTime;
address clonedCampaign;
address implementation;
}
struct BasicInfo {
string name;
string ipfsCID;
address owner;
uint256 startTime;
uint256 endTime;
}
struct PostInfo {
uint256 totalPosts;
uint256 totalEligibleContents;
uint256 totalEffectiveKPIs;
uint256 lastPostTime;
uint256 lastUpdatedTime;
uint256 applicationFee;
}
struct Token {
string name;
string symbol;
uint256 decimals;
}
struct BudgetInfo {
address token;
uint128 maxPerPost;
uint128 reservedAmount;
}
struct PostData {
string postLink;
address applicant;
}
struct QualificationData {
QA_METHOD method;
uint88 pct;
address oracle;
string kind;
}
struct KPI {
uint8 pct;
uint248 min;
uint256 ratio;
string method;
string extra;
}
struct SocialKPIs {
string social;
KPI[] kpis;
}
/**
* @title IEchoCampaign
* @notice Defines the interface for Echo Campaign contracts, which manage campaign
* settings, budgets, KPIs, content submissions, and reward distribution.
* @dev This interface provides function definitions for interacting with campaign
* data, including retrieving budget details, tracking content KPIs, and
* managing administrative actions.
* @author Dynabits.org
*/
interface IEchoCampaign {
/*******************************\
|*-*-*-* EXTERNALS *-*-*-*-*|
\*******************************/
/**
* @notice Initializes the contract with provided parameters.
* @dev This function can only be called by the factory contract.
* It sets the IPFS CID, budget information, social KPIs, and qualification data.
* Additionally, it registers action kinds based on QA methods and social KPIs.
* @param ipfsCID The IPFS Content Identifier (CID) associated with the data.
* @param budgetInfo_ Struct containing budget-related information.
* @param socialKPIs_ Struct containing social Key Performance Indicators (KPIs).
* @param qaData_ An array of QualificationData structs defining QA methods and kinds.
* Reverts:
* - `ONLY_FACTORY` if the caller is not the factory contract.
* - `DUPLICATE_QA` if there is a duplicate qualification.
* - `DUPLICATE_KPI` if there is a duplicate KPI.
*/
function init(
string calldata ipfsCID,
BudgetInfo calldata budgetInfo_,
SocialKPIs calldata socialKPIs_,
QualificationData[] calldata qaData_
) external;
/**
* @notice Toggles the campaign's pause state.
* @dev Only callable by the protocol administrator.
*
* Emits:
* - `CampaignPauseToggled` with the new state.
*
* Reverts:
* - `UNAUTHORIZED_CALLER` if the caller is not the protocol administrator.
*/
function togglePauseCampaign() external;
/**
* @notice Finalizes a campaign that is in progress.
* @dev Only callable by the protocol administrator when the campaign status is `inProgress`.
* Refunds any unspent budget and marks the campaign as finalized.
*
* Emits:
* - `CampaignFinalized` with the protocol administrator as the finalizer and the refunded amount.
*
* Reverts:
* - `UNAUTHORIZED_CALLER` if the caller is not the protocol administrator.
* - `ONLY_IN_PROGRESS_STATUS` if the campaign is not currently in progress.
*/
function finalizeInProgressCampaign() external;
/**
* @notice Increases the campaign budget by a specified amount.
* @dev Transfers the specified amount of tokens from the caller to the campaign
* and updates the reserved budget.
* @param amount The amount of tokens to add to the campaign budget.
*
* Emits:
* - `BudgetIncreased` with the added amount, previous budget, new budget, and sender address.
*
* Reverts:
* - `ONLY_UPCOMING_OR_IN_PROGRESS_STATUS` if the campaign is not in `upcoming` or `inProgress` status.
*/
function increaseBudget(uint256 amount) external;
/**
* @notice Submits content for the campaign.
* @dev Adds a content link to the campaign without performing authentication checks at submission time.
* Authentication is enforced at the claiming stage.
* @param contentLink The link to the submitted content.
*
* Reverts:
* - `ONLY_IN_PROGRESS_STATUS` if the campaign is not in `inProgress` status.
* - `INVALID_CONTENT_LINK` if the provided content link is empty.
*/
function applyContent(string calldata contentLink) external;
/**
* @notice Finalizes a campaign that has reached the finished status.
* @dev Ensures the campaign is in the `finished` status before finalization.
* Refunds any unspent budget and marks the campaign as finalized.
*
* Emits:
* - `CampaignFinalized` with the finalizer and the refunded amount.
*
* Reverts:
* - `ONLY_FINISHED_CAMPAIGN_STATUS` if the campaign is not in `finished` status.
*/
function finalizeFinishedCampaign() external;
/**
* @notice Updates the KPI metrics for a specific content link.
* @dev Modifies the total and per-content effective KPIs based on the provided values.
* If the content has no prior KPI value, it is counted as an eligible content.
*
* @param contentLink The unique identifier (link) of the content being updated.
* @param additionalContentEffectiveKPI The KPI value to be added or subtracted.
* @param additional A boolean indicating whether to increase or decrease the KPI.
*
* @return totalEffectiveKPIs The updated total sum of all effective KPIs.
* @return contentEffectiveKPI_ The updated KPI for the specified content.
* @return totalEligibleContents The updated count of eligible contents.
* @return eligibilityPolarity must be either 1, 0, or -1 and determines how the total eligible content count is updated.
*
* Requirements:
* - Caller must be the EchoContetns contract.
*/
function updateKPIs(
string calldata contentLink,
uint256 additionalContentEffectiveKPI,
bool additional
)
external
returns (
uint256 totalEffectiveKPIs,
uint256 contentEffectiveKPI_,
uint256 totalEligibleContents,
int8 eligibilityPolarity
);
/**
* @notice Allows the reward claim for registered and eligible content whose author has been registered by the oracle.
* @dev This function verifies that the campaign status is `claimable`, ensures the content is registered,
* checks its effective KPI score, calculates the reward based on the KPI ratio, and distributes
* the appropriate amount. If a max reward per post is defined, any excess funds are refunded.
* Once all eligible claims have been processed, the campaign is finished.
* @param contentLink The unique identifier (URL) of the content for which the claim is being made.
*
* Requirements:
* - The campaign status must be `claimable`.
* - The content must be registered under the campaign.
* - The content must have an effective KPI score greater than zero.
*
* Emits:
* - `Claimed` event upon successful claim, indicating the content, recipient, reward amount, and refund amount.
* - `CampaignFinished` event is emitted when all eligible contents have been claimed and the campaign is finished.
*
* Reverts:
* - `ONLY_CLAIMABLE_STATUS()` if the campaign is not in the `claimable` status.
* - `INVALID_CONTENT_LINK()` if the content is not registered in the campaign.
* - `NO_CONTENT_AUTHOR()` if the content's author has not been registered by the oracle before claiming.
* - `NOT_ELIGIBLE_FOR_WITHDRAW()` if the content has no effective KPI score.
*/
function claim(string calldata contentLink) external;
/*****************************\
|-*-*-*-* GETTERS *-*-*-*-|
\*****************************/
/// @notice Retrieves the budget information for the campaign.
/// @return budgetInfo_ The budget details, including reserved amount and token information.
function budgetInfo() external view returns (BudgetInfo memory budgetInfo_);
/// @notice Retrieves the social KPIs associated with the campaign.
/// @return socialKPIs_ The set of social KPIs used for evaluating campaign performance.
function socialKPIs() external view returns (SocialKPIs memory socialKPIs_);
/// @notice Retrieves the qualification data for the campaign.
/// @return qaData_ An array of qualification data required for content evaluation.
function qaData()
external
view
returns (QualificationData[] memory qaData_);
/// @notice Checks whether a specific action kind is registered in the campaign.
/// @param actionKind The string representation of the action kind to check.
/// @return isRegistered A boolean indicating whether the action kind is registered.
function registeredActionKind(string calldata actionKind)
external
view
returns (bool);
/// @notice Retrieves the effective KPI score for a given content link.
/// @param contentLink The unique identifier (URL or hash) of the submitted content.
/// @return kpiScore The effective KPI score assigned to the content.
function contentEffectiveKPI(string calldata contentLink)
external
view
returns (uint256);
/**
* @notice Estimates the reward amount for content based on provided QA and KPI data.
* @dev Ensures QA and KPI inputs match the campaign's predefined requirements.
* @param qas An array of qualification data for the content.
* @param kpis An array of KPI data for the content.
* @return rewardAmount The estimated reward amount in campaign token units.
*/
function rewardEstimation(
QualificationData[] calldata qas,
KPI[] calldata kpis
) external view returns (uint256 rewardAmount);
/**
* @notice Retrieves comprehensive details about the campaign.
* @return basicInfo_ Basic campaign details such as name, owner, and time period.
* @return initData_ Initialization data from the campaign factory.
* @return budgetInfo_ Budget details including token and reserved amounts.
* @return token_ Token metadata such as name, symbol, and decimals.
* @return socialKPIs_ The social KPI details of the campaign.
* @return qaData_ The qualification data required for content.
* @return postInfo_ Statistics related to campaign content submissions.
* @return status_ The current status of the campaign.
*/
function details()
external
view
returns (
BasicInfo memory basicInfo_,
CampaignInitData memory initData_,
BudgetInfo memory budgetInfo_,
Token memory token_,
SocialKPIs memory socialKPIs_,
QualificationData[] memory qaData_,
PostInfo memory postInfo_,
Status status_
);
/**
* @notice Returns the address of the person who created the specified content and is eligible to claim it.
* @param contentLink The URL of the submitted content.
* @return The wallet address of the content creator for the given content.
May return the zero address (0x0) if no record is found in the oracle.
*/
function getContentCreator(string calldata contentLink)
external
view
returns (address);
/// @notice Retrieves the current status of the campaign.
/// @return status_ The campaign's status as an enum value.
function currentStatus() external view returns (Status status_);
/// @notice Calculates the application fee required for content submission.
/// @return fee The total application fee in campaign token units.
function applicationFee() external view returns (uint256 fee);
/// @notice Retrieves the campaign's name.
/// @return campaignName The human-readable name of the campaign.
function name() external view returns (string memory campaignName);
/// @notice Retrieves the address of the Echo Contents contract.
/// @return contentsAddress The address of the content management contract.
function contents() external view returns (address contentsAddress);
/// @notice Retrieves the address of the Echo Administration contract.
/// @return adminAddress The address of the admin contract.
function admin() external pure returns (address adminAddress);
/// @notice Retrieves the address of the campaign factory.
/// @return factoryAddress The address of the campaign factory contract.
function factory() external pure returns (address factoryAddress);
/// @notice Retrieves the owner of the campaign.
/// @return ownerAddress The address of the campaign owner.
function owner() external pure returns (address ownerAddress);
/// @notice Retrieves the refund address for the campaign.
/// @return refundAddr The address where refunds are sent.
function refundAddress() external pure returns (address refundAddr);
/// @notice Retrieves the hashed name of the campaign.
/// @return campaignNameHash The bytes32 representation of the campaign name.
function nameHash() external pure returns (bytes32 campaignNameHash);
/// @notice Retrieves the start time of the campaign.
/// @return startTimestamp The Unix timestamp of the campaign start time.
function startTime() external pure returns (uint256 startTimestamp);
/// @notice Retrieves the end time of the campaign.
/// @return endTimestamp The Unix timestamp of the campaign end time.
function endTime() external pure returns (uint256 endTimestamp);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {IEchoAdministration} from "./IEchoAdministration.sol";
import {IEchoCampaignFactory} from "./IEchoCampaignFactory.sol";
import {QA_METHOD} from "./IEchoCampaign.sol";
import {UD60x18} from "@prb/math/src/UD60x18.sol";
/***************************\
|*-*-*-* TYPES *-*-*-*|
\***************************/
struct ConfigOracleData {
string kind;
bytes value;
}
struct QualificationOracleData {
QA_METHOD method;
uint256 pct;
string kind;
}
struct KPIsOracleData {
string method;
uint256 value;
}
struct SocialKPIsOracleData {
string social;
KPIsOracleData[] kpis;
}
struct CampaignAndContent {
address campaign;
string content;
}
struct CampaignContentInfo {
uint128 lastUpdatedTime;
uint128 lastPostTime;
string[] contents;
}
struct ContentDetails {
bool contentRegistered;
uint8 totalQaKinds;
uint240 lastUpdatedTime;
UD60x18 contentOverallQaScore;
mapping(string => uint256) actionKindToValue;
}
struct ContentsData {
uint256 totalEffectiveKPIs;
uint256 totalPosts;
uint256 totalEligibleContents;
}
/**
* @title Echo Contents Interface
* @notice Manages content within Echo campaigns by enabling registration, updates, and evaluation.
* @dev This interface facilitates content tracking, quality assessment through oracles, and KPI updates.
* - Allows campaigns to associate and manage content.
* - Supports qualification and configuration updates via oracles and protocol admin.
* - Enables KPI tracking based on predefined metrics.
* - Provides getter functions for efficient content retrieval and analytics.
* @author Dynabits.org
*/
interface IEchoContents {
/*******************************\
|*-*-*-* EXTERNALS *-*-*-*-*|
\*******************************/
/**
* @notice Registers a new content link for a campaign.
* @dev Associates content with a campaign, ensuring only authorized campaigns can register.
* @param contentLink The unique identifier (e.g., IPFS hash) of the content.
* @param campaignMaxPerPost The campaign’s maximum payment per post.
*
* Requirements:
* - The caller must be a registered campaign.
* - The content must not have been previously registered for the same campaign.
*
* Effects:
* - Stores the content reference associated with the campaign.
* - Updates the campaign’s content records, including the last post timestamp.
*
* Emits:
* - `ContentApplied` upon successful registration.
*
* Reverts:
* - `ACCESS_DENIED` if the caller is not a registered campaign.
* - `CONTENT_REGISTERED_FOR_CAMPAIGN_BEFORE` if already registered.
*/
function addContentLink(string calldata contentLink, uint256 campaignMaxPerPost) external;
/**
* @notice Updates configuration and qualification data for a specific content in a campaign.
* @dev Allows oracles or the protocol admin to set content-related data.
* @param campaign The campaign's address.
* @param contentLink The unique identifier of the content.
* @param configs An array of configuration data from the oracle.
* @param qualifications Qualification data used to evaluate the content.
*
* Requirements:
* - At least one configuration or qualification must be provided.
* - The caller must be the protocol admin or an authorized oracle.
* - If the caller is an oracle, they must not be banned.
* - The content must be registered under the campaign.
* - Configuration updates require appropriate oracle permissions.
* - Qualification updates must adhere to campaign-specific rules and valid percentage ranges.
*
* Emits:
* - `ContentConfigsAdded` on successful configuration update.
* - `NewQualificationsSettled` on successful qualification update.
* - `ContentQAoverallScore` if the final QA score is calculated.
* - `ReadyForKPIupdates` if the content qualifies for KPI updates.
*
* Reverts:
* - `UNDEFINED_JOB` if no configurations or qualifications are provided.
* - `ORACLE_IS_BAN` if the caller is a banned oracle.
* - `CONTENT_DOESNT_EXIST` if the content is unregistered.
* - `ORACLE_DOESNT_HAVE_REQUIRED_ACCESS` if the caller lacks permissions.
* - `GIVEN_QA_KIND_DOESNT_EXIST_ON_CAMPAIGN` if an invalid qualification is provided.
* - `QA_DATA_SETTLED_BEFORE` if qualifications were previously set and the caller is not the admin.
* - `CHECK_QA_PCT` if the qualification percentage is out of range.
*/
function setContentData(
address campaign,
string calldata contentLink,
ConfigOracleData[] calldata configs,
QualificationOracleData[] calldata qualifications
) external;
/**
* @notice Updates the KPI data for registered content within a campaign.
* @dev Only the protocol admin or an authorized KPI oracle can update KPIs.
* If all required KPIs are met, the Effective KPI formula is applied.
* @param campaign The address of the campaign.
* @param contentLink The unique identifier of the content (e.g., IPFS hash).
* @param socialKPIs The KPI data associated with the campaign’s social platform.
*
* Requirements:
* - The caller must be the protocol admin or an authorized KPI oracle.
* - A banned oracle cannot update KPIs unless they are the admin.
* - The content must be registered under the specified campaign.
* - The campaign must be in an `InProgress`, `Paused`, or `Finished` state.
* - The content must have a valid non-zero QA score.
* - The KPI action must be registered under the campaign.
* - The oracle must have the required access to modify the KPI data.
*
* Effects:
* - Updates KPI values for the specified content and campaign.
* - Computes the Effective KPI value based on campaign rules and content QA score.
* - Updates the campaign’s total Effective KPIs and eligible content count.
*
* Emits:
* - `KPIsUpdated` when a KPI update is successfully processed.
* - `ContentEffectiveKPIupdated` when the Effective KPI score is updated.
*
* Reverts:
* - `ORACLE_IS_BAN` if the caller is a banned oracle.
* - `CONTENT_DOESNT_EXIST` if the content is not registered.
* - `ONLY_IN_PROGRESS_OR_PAUSED_OR_FINISHED_CAMPAIGN` if the campaign is in an invalid state.
* - `ZERO_CONTENT_QA_SCORE` if the content has a QA score of zero.
* - `DIFFERENT_SOCIALS` if the provided KPI does not match the campaign’s social KPI.
* - `GIVEN_KPI_KIND_DOESNT_EXIST_ON_CAMPAIGN` if the KPI type is not registered in the campaign.
* - `ORACLE_DOESNT_HAVE_REQUIRED_ACCESS` if the caller lacks KPI update permissions.
*/
function updateKPIs(
address campaign,
string calldata contentLink,
SocialKPIsOracleData calldata socialKPIs
) external;
/*****************************\
|-*-*-*-* GETTERS *-*-*-*-|
\*****************************/
/**
* @notice Retrieve the registered content along with its associated campaign address.
* @param index The index in the contents array.
* @return campaign The address of the campaign associated with the content.
* @return content The registered content.
*/
function contents(uint256 index)
external
view
returns (address campaign, string memory content);
/// @notice Retrieves the address of the Echo Administration contract.
/// @return The address of the Echo Administration contract.
function ECHO_ADMIN() external view returns (IEchoAdministration);
/// @notice Retrieves the address of the Echo Campaign Factory contract.
/// @return The address of the Echo Campaign Factory contract.
function ECHO_FACTORY() external view returns (IEchoCampaignFactory);
/**
* @notice Retrieve the content-related timestamps for a given campaign.
* @param campaign The address of the campaign.
* @return lastUpdatedTime The timestamp of the last update made to the campaign's content.
* @return lastPostTime The timestamp of the last content post associated with the campaign.
*/
function campaignContentInfo(address campaign)
external
view
returns (uint256 lastUpdatedTime, uint256 lastPostTime);
/**
* @notice Retrieve information about a specific content registered for a given campaign.
* @param campaign The address of the campaign associated with the content.
* @param contentLink The unique link or identifier of the content.
* @param actionKind The concatenated social platform and the intended method, such as "X: Like" (optional).
* @return contentRegistered A boolean indicating whether the content is registered under the campaign.
* @return totalQaKinds The total number of qualifications set for the content in the campaign.
* @return lastUpdatedTime The timestamp of the last update made to the content.
* @return actionKindValue The corresponding value for the provided action kind, if applicable.
*/
function contentToCampaign(
address campaign,
string calldata contentLink,
string calldata actionKind
)
external
view
returns (
bool contentRegistered,
uint256 totalQaKinds,
uint256 lastUpdatedTime,
uint256 actionKindValue
);
/**
* @notice Returns recent registered contents and their campaigns in paginated format.
* @dev If page = 0, returns the last 10 registered contents.
* @param page The page number.
* @return currentPage The current page number.
* @return totalPages The total number of pages.
* @return pagedArray A list of 10 recent contents with campaign addresses.
*/
function paginatedContentsWithCampaigns(uint256 page)
external
view
returns (
uint256 currentPage,
uint256 totalPages,
CampaignAndContent[] memory pagedArray
);
/**
* @notice Returns recent registered contents for a specific campaign in paginated format.
* @dev If page = 0, returns the last 10 contents for the campaign.
* @param campaign The campaign's address.
* @param page The page number.
* @return currentPage The current page number.
* @return totalPages The total number of pages.
* @return pagedArray A list of 10 recent contents associated with the campaign.
*/
function paginatedCampaignContents(uint256 page, address campaign)
external
view
returns (
uint256 currentPage,
uint256 totalPages,
string[] memory pagedArray
);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {CampaignInitData, BudgetInfo, SocialKPIs, QualificationData} from "./IEchoCampaign.sol";
/***************************\
|*-*-*-* TYPES *-*-*-*|
\***************************/
/// @notice Structure containing the initialization parameters for a new campaign.
/// @dev This struct encapsulates all key configurations required when creating a campaign.
struct InitCampaign {
uint256 startTime; /// Start time of the campaign (Unix timestamp).
uint256 endTime; /// End time of the campaign (Unix timestamp).
address refundAddress; /// Address to receive refunds (required if maxPerPost is set).
string ipfsCID; /// IPFS content identifier (CID) for storing metadata.
BudgetInfo budgetInfo; /// Budget-related configurations for the campaign.
SocialKPIs socialKPIs; /// Social Key Performance Indicators (KPIs) for the campaign.
QualificationData[] qaData; /// Array of qualification methods used for campaign validation.
}
/// @title Echo Campaign Factory Interface
/// @notice This interface defines the core functionality for deploying campaign contracts.
/// @author Dynabits.org
interface IEchoCampaignFactory {
/*******************************\
|*-*-*-* EXTERNALS *-*-*-*-*|
\*******************************/
/// @notice Updates the campaign implementation contract.
/// @dev This function allows the protocol admin to change the underlying campaign logic by deploying a new contract.
/// @param newImplementation The address of the new campaign implementation.
function changeImplementation(address newImplementation) external;
/**
* @notice Deploys a new campaign contract with the specified parameters.
* @dev Performs necessary validation checks before deployment, including:
* - Ensuring valid input parameters (e.g., campaign name, budget, duration).
* - Enforcing compliance with qualification and KPI requirements.
* - Deducting protocol fees and transferring budget to the campaign contract.
* @param owner The address of the campaign owner.
* @param name The unique name of the campaign (must be between 8 and 64 characters).
* @param initCampaign A struct containing all initial parameters for campaign deployment.
* @return clonedCampaign The address of the newly deployed campaign contract.
*
* Requirements:
* - `owner` must be a valid, non-zero address.
* - `name` must be unique and between 8 to 64 characters in length.
* - `initCampaign.refundAddress` must be valid if `maxPerPost` is set.
* - `initCampaign.ipfsCID` must be a non-empty string.
* - The campaign start time must not be in the past.
* - The campaign end time must be later than the start time.
* - The campaign duration must not exceed the protocol's maximum allowed time.
* - The campaign's budget token must be whitelisted.
* - `maxPerPost` must not exceed `reservedAmount`, if set.
* - At least one qualification method (QA method) must be provided.
* - The total qualification percentages (`pct`) must sum up to 100%.
* - At least one KPI must be registered.
* - The sum of KPI percentages must be exactly 100%.
* - The provided social network and KPIs must be allowed by the protocol.
*
* Effects:
* - Deploys a new campaign contract as a clone of the base implementation.
* - Deducts the protocol fee from the reserved campaign budget.
* - Initializes the campaign contract with the provided parameters.
* - Transfers the remaining budget to the newly created campaign.
* - Stores campaign metadata for tracking and uniqueness validation.
*
* Emits:
* - `CampaignCreated` event upon successful deployment of the campaign.
*
* Reverts:
* - `ZERO_ADDRESS` if `owner` or `refundAddress` (when required) is zero.
* - `NAME_LENGTH_LOWER_THAN_8_BYTES` if the name is too short.
* - `NAME_LENGTH_LONGER_THAN_64_BYTES` if the name is too long.
* - `NULL_CID` if the campaign IPFS CID is empty.
* - `NAME_EXISTS` if a campaign with the same name already exists.
* - `START_TIME_IN_PAST` if the campaign start time is invalid.
* - `END_TIME_LOWER_THAN_START_TIME` if the end time is before or equal to the start time.
* - `CHECK_MAX_CAMPAIGN_DURATION` if the duration exceeds the protocol’s limit.
* - `MAX_PER_POST_BIGGER_THAN_RESERVED_AMOUNT` if `maxPerPost` exceeds the available budget.
* - `NO_QUALIFICATION` if no qualification method is provided.
* - `QA_METHOD_OR_ORACLE_NOT_ALLOWED` if the qualification method or oracle is unauthorized.
* - `SUM_OF_QUALIFICATION_PARAMS_MUST_BE_100` if qualification percentages do not total 100%.
* - `NO_KPIS` if no KPIs are provided.
* - `SUM_OF_SOCIAL_KPI_PARAMS_MUST_BE_100` if KPI percentages do not sum to 100%.
* - `ONLY_ALLOWED_SOCIAL_KPIS` if an unapproved social KPI is used.
* - `SOCIAL_KPI_RATIO_IS_ZERO` if a KPI lacks a predefined ratio in the protocol.
*/
function createCampaign(
address owner,
string calldata name,
InitCampaign memory initCampaign
) external returns (address clonedCampaign);
/*****************************\
|-*-*-*-* GETTERS *-*-*-*-|
\*****************************/
/// @notice Retrieves the current implementation contract for campaigns.
/// @dev This address is used for cloning new campaign instances.
/// @return The address of the campaign implementation contract.
function implementation() external view returns (address);
/// @notice Retrieves the address of a deployed campaign by its index.
/// @param index The index of the campaign in the `allCampaigns` array.
/// @return The address of the deployed campaign.
function allCampaigns(uint256 index) external view returns (address);
/**
* @notice Retrieves the original campaign name from its hashed value.
* @dev Useful for verifying unique campaign names.
* @param nameHash The hashed value of the campaign name.
* @return The original campaign name.
*/
function campaignNameHashToName(bytes32 nameHash)
external
view
returns (string memory);
/// @notice Retrieves the address of the Echo Administration contract.
/// @return The address of the Echo Administration contract.
function ECHO_ADMIN() external view returns (address);
/// @notice Retrieves the address of the Echo Contents contract.
/// @return The address of the Echo Contents contract.
function ECHO_CONTENTS() external view returns (address);
/**
* @notice Retrieves initialization details for a specific campaign.
* @dev Provides useful data for off-chain applications and analytics.
* @param name The name of the deployed campaign.
* @return The timestamp of deployment, the deployed contract address, and the structural contract used.
*/
function campaignNameToInitData(string memory name)
external
view
returns (CampaignInitData memory);
/**
* @notice Retrieves a paginated list of recently deployed campaigns.
* @dev Returns 10 campaign addresses per page, with pagination for historical retrieval.
* - If `page = 0`, returns the 10 most recent campaigns.
* - Optimized for off-chain use cases as an alternative to event logs.
* @param page The page number requested.
* @return currentPage The current page number.
* @return totalPages The total number of available pages.
* @return pagedArray A list of up to 10 recently deployed campaign addresses.
*/
function paginatedCampaigns(uint256 page)
external
view
returns (
uint256 currentPage,
uint256 totalPages,
address[] memory pagedArray
);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
/***************************\
|*-*-*-* TYPES *-*-*-*|
\***************************/
enum ActionStatus {
NA,
Config,
QA,
KPI
}
struct ActionAndKind {
ActionStatus actionStatus;
string action;
string kind;
}
struct ModelData {
ActionStatus actionStatus;
uint8 socialKPIratio;
uint248 fee;
address oracle;
}
/// @title Echo Administration Interface
/// @notice Defines administrative functions for managing campaign settings, token whitelisting, and oracle permissions.
/// @author Dynabits.org
interface IEchoAdministration {
/*******************************\
|*-*-*-* EXTERNALS *-*-*-*-*|
\*******************************/
/// @notice Transfers protocol admin rights to a new address.
/// @dev Only the current protocol admin can call this function. The new admin address cannot be the zero address.
/// @param newProtocolAdmin The address of the new protocol admin.
function changeProtocolAdmin(address newProtocolAdmin) external;
/// @notice Sets the campaign fee rate as a percentage of the allocated token at creation.
/// @dev The value must be between 0 and 1e5.
/// @param newCampaignFeeRate The new campaign creation fee rate.
function changeCampaignFeeRate(uint24 newCampaignFeeRate) external;
/// @notice Sets the maximum campaign duration.
/// @dev A value of zero means there is no maximum campaign duration.
/// @param newMaxCampaignTime The maximum campaign duration in seconds.
function setMaxCampaignTime(uint72 newMaxCampaignTime) external;
/// @notice Whitelists a new ERC20 token for use in campaigns.
/// @dev A token cannot be whitelisted more than once. The whitelist status is checked during campaign creation.
/// @param token The ERC20 token address.
function whitelistToken(address token) external;
/// @notice Removes an ERC20 token from the whitelist, preventing its selection as a campaign budget token.
/// @param token The ERC20 token address.
function removeWhitelistedToken(address token) external;
/// @notice Toggles full access permissions for an oracle.
/// @dev This function can be used to temporarily or permanently disable an oracle’s access to campaign-related functionalities.
/// @param oracle The address of the oracle.
function toggleOracleFullAccess(address oracle) external;
/**
* @notice Sets or updates the model data for a given action and kind.
* @dev Configures an action-kind mapping with an associated oracle, fee, and status.
* @param actionAndKind The struct containing the action and kind identifiers.
* @param model The struct containing model details such as the oracle, fee, status, and social KPI ratio.
*
* Requirements:
* - `actionAndKind.action` and `actionAndKind.kind` must not be empty.
* - `model.actionStatus` must be one of `QA`, `KPI`, or `CONFIG`.
* - Actions must match their respective `actionStatus`:
* - `QA` actions: `QA-AI`, `QA-C`, `QA-V`
* - `KPI` actions: `KPI`
* - `CONFIG` actions: `CONFIG`
* - QA oracles must have a nonzero fee.
* - KPI oracles must have both a nonzero fee and a nonzero social KPI ratio.
* - CONFIG actions always have a zero fee.
*
* Effects:
* - Adds or updates an action-kind mapping.
* - Removes model data if `model.oracle` is zero.
*
* Emits:
* - `ModelSettled` if a model is set or updated.
* - `ModelRemoved` if a model is removed.
*
* Reverts:
* - `EMPTY_ACTION_OR_KIND_PROVIDED` if either action or kind is empty.
* - `NA_ACTION_STATUS` if the provided action status is `NA`.
* - `NOT_RELATIVE_ACTION` if the action does not match its required type.
* - `UNDEFINED_PROCESS` if a QA oracle has a fee but no valid address.
* - `ZERO_FEE_PROVIDED` if a required fee is zero.
* - `ZERO_SOCIAL_KPI_PROVIDED` if a required social KPI ratio is zero.
*/
function setModel(
ActionAndKind calldata actionAndKind,
ModelData calldata model
) external;
/*****************************\
|-*-*-*-* GETTERS *-*-*-*-|
\*****************************/
/// @notice Returns the campaign creation fee rate as a percentage.
/// @dev This value is deducted from the allocated budget immediately upon campaign creation.
/// @return The campaign creation fee rate.
function campaignFeeRate() external view returns (uint24);
/// @notice Returns the maximum allowed duration for campaigns.
/// @dev A campaign’s duration (end time - start time) must be less than or equal to this value at creation.
/// @return The maximum campaign duration in seconds.
function maxCampaignTime() external view returns (uint72);
/// @notice Returns the protocol admin address.
/// @dev The protocol admin has full access to infrastructural modifications.
/// @return The address of the protocol admin.
function protocolAdmin() external view returns (address);
/// @notice Checks if a given ERC20 token is whitelisted for campaign use.
/// @param token The ERC20 token address.
/// @return A boolean indicating whether the token is whitelisted.
function whitelistedToken(address token) external view returns (bool);
/**
* @notice Checks if an oracle has been banned.
* @dev A banned oracle loses all permissions to interact with EchoContents and campaign contracts.
* @param oracle The address of the oracle.
* @return A boolean indicating whether the oracle is banned.
*/
function disallowedOracle(address oracle) external view returns (bool);
/**
* @notice Returns the number of available methods and parameters for a given action.
* @dev Examples:
* - KPI actions (e.g., `X`) can have parameters like `Like` and `Retweet`.
* - Qualification actions (`QA-V`) can have parameters like `Accuracy` and `Originality`.
* @param action The action type, such as `X`, `QA-V`, or `Config`.
* @return The number of available methods for the action.
*/
function totalKindMethods(string calldata action)
external
view
returns (uint256);
/**
* @notice Retrieves the original action and kind from a given hashed identifier.
* @dev Useful for reducing gas costs and simplifying comparisons.
* @param hashedActionKind The hash of the concatenated action and kind.
* @return The decoded action and kind as a string.
*/
function hashToActionKind(bytes32 hashedActionKind)
external
view
returns (string memory);
/**
* @notice Returns model data associated with an oracle and a hashed action-kind.
* @param oracle_ The address of the oracle.
* @param hashedActionKind The hashed action-kind identifier.
* @return actionStatus The action status of the model.
* @return socialKPIratio The social KPI ratio.
* @return fee The associated fee for the action.
* @return oracle The address of the oracle managing the action.
*/
function modelData(address oracle_, bytes32 hashedActionKind)
external
view
returns (
ActionStatus actionStatus,
uint8 socialKPIratio,
uint248 fee,
address oracle
);
/**
* @notice Checks if an oracle has the required permissions for given action-kind hashes.
* @dev Access is determined based on the oracle's address:
* - `address(0)`: Checks if the hashed action-kind exists for KPI.
* - `address(1)`: Checks if the hashed action-kind exists for CONFIG.
* - Any other address: Checks if the oracle has QA access.
* @param oracle The oracle’s address.
* @param hashedActionsKind An array of hashed action-kind values to verify.
* @return A boolean indicating whether the oracle is authorized.
*/
function allowedActionKind(
address oracle,
bytes32[] calldata hashedActionsKind
) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
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 value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` 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 value) external returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import "./Casting.sol" as Casting;
import "./Helpers.sol" as Helpers;
import "./Math.sol" as Math;
/// @notice The unsigned 60.18-decimal fixed-point number representation, which can have up to 60 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the Solidity type uint256.
/// @dev The value type is defined here so it can be imported in all other files.
type UD60x18 is uint256;
/*//////////////////////////////////////////////////////////////////////////
CASTING
//////////////////////////////////////////////////////////////////////////*/
using {
Casting.intoSD1x18,
Casting.intoUD2x18,
Casting.intoSD59x18,
Casting.intoUint128,
Casting.intoUint256,
Casting.intoUint40,
Casting.unwrap
} for UD60x18 global;
/*//////////////////////////////////////////////////////////////////////////
MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
// The global "using for" directive makes the functions in this library callable on the UD60x18 type.
using {
Math.avg,
Math.ceil,
Math.div,
Math.exp,
Math.exp2,
Math.floor,
Math.frac,
Math.gm,
Math.inv,
Math.ln,
Math.log10,
Math.log2,
Math.mul,
Math.pow,
Math.powu,
Math.sqrt
} for UD60x18 global;
/*//////////////////////////////////////////////////////////////////////////
HELPER FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
// The global "using for" directive makes the functions in this library callable on the UD60x18 type.
using {
Helpers.add,
Helpers.and,
Helpers.eq,
Helpers.gt,
Helpers.gte,
Helpers.isZero,
Helpers.lshift,
Helpers.lt,
Helpers.lte,
Helpers.mod,
Helpers.neq,
Helpers.not,
Helpers.or,
Helpers.rshift,
Helpers.sub,
Helpers.uncheckedAdd,
Helpers.uncheckedSub,
Helpers.xor
} for UD60x18 global;
/*//////////////////////////////////////////////////////////////////////////
OPERATORS
//////////////////////////////////////////////////////////////////////////*/
// The global "using for" directive makes it possible to use these operators on the UD60x18 type.
using {
Helpers.add as +,
Helpers.and2 as &,
Math.div as /,
Helpers.eq as ==,
Helpers.gt as >,
Helpers.gte as >=,
Helpers.lt as <,
Helpers.lte as <=,
Helpers.or as |,
Helpers.mod as %,
Math.mul as *,
Helpers.neq as !=,
Helpers.not as ~,
Helpers.sub as -,
Helpers.xor as ^
} for UD60x18 global;// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import { wrap } from "./Casting.sol";
import {
uEXP_MAX_INPUT,
uEXP2_MAX_INPUT,
uHALF_UNIT,
uLOG2_10,
uLOG2_E,
uMAX_UD60x18,
uMAX_WHOLE_UD60x18,
UNIT,
uUNIT,
uUNIT_SQUARED,
ZERO
} from "./Constants.sol";
import { UD60x18 } from "./ValueType.sol";
/*//////////////////////////////////////////////////////////////////////////
MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Calculates the arithmetic average of x and y using the following formula:
///
/// $$
/// avg(x, y) = (x & y) + ((xUint ^ yUint) / 2)
/// $$
///
/// In English, this is what this formula does:
///
/// 1. AND x and y.
/// 2. Calculate half of XOR x and y.
/// 3. Add the two results together.
///
/// This technique is known as SWAR, which stands for "SIMD within a register". You can read more about it here:
/// https://devblogs.microsoft.com/oldnewthing/20220207-00/?p=106223
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// @param x The first operand as a UD60x18 number.
/// @param y The second operand as a UD60x18 number.
/// @return result The arithmetic average as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function avg(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
uint256 xUint = x.unwrap();
uint256 yUint = y.unwrap();
unchecked {
result = wrap((xUint & yUint) + ((xUint ^ yUint) >> 1));
}
}
/// @notice Yields the smallest whole number greater than or equal to x.
///
/// @dev This is optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional
/// counterparts. See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
///
/// Requirements:
/// - x must be less than or equal to `MAX_WHOLE_UD60x18`.
///
/// @param x The UD60x18 number to ceil.
/// @param result The smallest whole number greater than or equal to x, as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function ceil(UD60x18 x) pure returns (UD60x18 result) {
uint256 xUint = x.unwrap();
if (xUint > uMAX_WHOLE_UD60x18) {
revert Errors.PRBMath_UD60x18_Ceil_Overflow(x);
}
assembly ("memory-safe") {
// Equivalent to `x % UNIT`.
let remainder := mod(x, uUNIT)
// Equivalent to `UNIT - remainder`.
let delta := sub(uUNIT, remainder)
// Equivalent to `x + remainder > 0 ? delta : 0`.
result := add(x, mul(delta, gt(remainder, 0)))
}
}
/// @notice Divides two UD60x18 numbers, returning a new UD60x18 number.
///
/// @dev Uses {Common.mulDiv} to enable overflow-safe multiplication and division.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv}.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv}.
///
/// @param x The numerator as a UD60x18 number.
/// @param y The denominator as a UD60x18 number.
/// @param result The quotient as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function div(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
result = wrap(Common.mulDiv(x.unwrap(), uUNIT, y.unwrap()));
}
/// @notice Calculates the natural exponent of x using the following formula:
///
/// $$
/// e^x = 2^{x * log_2{e}}
/// $$
///
/// @dev Requirements:
/// - x must be less than 133_084258667509499441.
///
/// @param x The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp(UD60x18 x) pure returns (UD60x18 result) {
uint256 xUint = x.unwrap();
// This check prevents values greater than 192e18 from being passed to {exp2}.
if (xUint > uEXP_MAX_INPUT) {
revert Errors.PRBMath_UD60x18_Exp_InputTooBig(x);
}
unchecked {
// Inline the fixed-point multiplication to save gas.
uint256 doubleUnitProduct = xUint * uLOG2_E;
result = exp2(wrap(doubleUnitProduct / uUNIT));
}
}
/// @notice Calculates the binary exponent of x using the binary fraction method.
///
/// @dev See https://ethereum.stackexchange.com/q/79903/24693
///
/// Requirements:
/// - x must be less than 192e18.
/// - The result must fit in UD60x18.
///
/// @param x The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp2(UD60x18 x) pure returns (UD60x18 result) {
uint256 xUint = x.unwrap();
// Numbers greater than or equal to 192e18 don't fit in the 192.64-bit format.
if (xUint > uEXP2_MAX_INPUT) {
revert Errors.PRBMath_UD60x18_Exp2_InputTooBig(x);
}
// Convert x to the 192.64-bit fixed-point format.
uint256 x_192x64 = (xUint << 64) / uUNIT;
// Pass x to the {Common.exp2} function, which uses the 192.64-bit fixed-point number representation.
result = wrap(Common.exp2(x_192x64));
}
/// @notice Yields the greatest whole number less than or equal to x.
/// @dev Optimized for fractional value inputs, because every whole value has (1e18 - 1) fractional counterparts.
/// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
/// @param x The UD60x18 number to floor.
/// @param result The greatest whole number less than or equal to x, as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function floor(UD60x18 x) pure returns (UD60x18 result) {
assembly ("memory-safe") {
// Equivalent to `x % UNIT`.
let remainder := mod(x, uUNIT)
// Equivalent to `x - remainder > 0 ? remainder : 0)`.
result := sub(x, mul(remainder, gt(remainder, 0)))
}
}
/// @notice Yields the excess beyond the floor of x using the odd function definition.
/// @dev See https://en.wikipedia.org/wiki/Fractional_part.
/// @param x The UD60x18 number to get the fractional part of.
/// @param result The fractional part of x as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function frac(UD60x18 x) pure returns (UD60x18 result) {
assembly ("memory-safe") {
result := mod(x, uUNIT)
}
}
/// @notice Calculates the geometric mean of x and y, i.e. $\sqrt{x * y}$, rounding down.
///
/// @dev Requirements:
/// - x * y must fit in UD60x18.
///
/// @param x The first operand as a UD60x18 number.
/// @param y The second operand as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function gm(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
uint256 xUint = x.unwrap();
uint256 yUint = y.unwrap();
if (xUint == 0 || yUint == 0) {
return ZERO;
}
unchecked {
// Checking for overflow this way is faster than letting Solidity do it.
uint256 xyUint = xUint * yUint;
if (xyUint / xUint != yUint) {
revert Errors.PRBMath_UD60x18_Gm_Overflow(x, y);
}
// We don't need to multiply the result by `UNIT` here because the x*y product picked up a factor of `UNIT`
// during multiplication. See the comments in {Common.sqrt}.
result = wrap(Common.sqrt(xyUint));
}
}
/// @notice Calculates the inverse of x.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x must not be zero.
///
/// @param x The UD60x18 number for which to calculate the inverse.
/// @return result The inverse as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function inv(UD60x18 x) pure returns (UD60x18 result) {
unchecked {
result = wrap(uUNIT_SQUARED / x.unwrap());
}
}
/// @notice Calculates the natural logarithm of x using the following formula:
///
/// $$
/// ln{x} = log_2{x} / log_2{e}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
/// - The precision isn't sufficiently fine-grained to return exactly `UNIT` when the input is `E`.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The UD60x18 number for which to calculate the natural logarithm.
/// @return result The natural logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function ln(UD60x18 x) pure returns (UD60x18 result) {
unchecked {
// Inline the fixed-point multiplication to save gas. This is overflow-safe because the maximum value that
// {log2} can return is ~196_205294292027477728.
result = wrap(log2(x).unwrap() * uUNIT / uLOG2_E);
}
}
/// @notice Calculates the common logarithm of x using the following formula:
///
/// $$
/// log_{10}{x} = log_2{x} / log_2{10}
/// $$
///
/// However, if x is an exact power of ten, a hard coded value is returned.
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The UD60x18 number for which to calculate the common logarithm.
/// @return result The common logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function log10(UD60x18 x) pure returns (UD60x18 result) {
uint256 xUint = x.unwrap();
if (xUint < uUNIT) {
revert Errors.PRBMath_UD60x18_Log_InputTooSmall(x);
}
// Note that the `mul` in this assembly block is the standard multiplication operation, not {UD60x18.mul}.
// prettier-ignore
assembly ("memory-safe") {
switch x
case 1 { result := mul(uUNIT, sub(0, 18)) }
case 10 { result := mul(uUNIT, sub(1, 18)) }
case 100 { result := mul(uUNIT, sub(2, 18)) }
case 1000 { result := mul(uUNIT, sub(3, 18)) }
case 10000 { result := mul(uUNIT, sub(4, 18)) }
case 100000 { result := mul(uUNIT, sub(5, 18)) }
case 1000000 { result := mul(uUNIT, sub(6, 18)) }
case 10000000 { result := mul(uUNIT, sub(7, 18)) }
case 100000000 { result := mul(uUNIT, sub(8, 18)) }
case 1000000000 { result := mul(uUNIT, sub(9, 18)) }
case 10000000000 { result := mul(uUNIT, sub(10, 18)) }
case 100000000000 { result := mul(uUNIT, sub(11, 18)) }
case 1000000000000 { result := mul(uUNIT, sub(12, 18)) }
case 10000000000000 { result := mul(uUNIT, sub(13, 18)) }
case 100000000000000 { result := mul(uUNIT, sub(14, 18)) }
case 1000000000000000 { result := mul(uUNIT, sub(15, 18)) }
case 10000000000000000 { result := mul(uUNIT, sub(16, 18)) }
case 100000000000000000 { result := mul(uUNIT, sub(17, 18)) }
case 1000000000000000000 { result := 0 }
case 10000000000000000000 { result := uUNIT }
case 100000000000000000000 { result := mul(uUNIT, 2) }
case 1000000000000000000000 { result := mul(uUNIT, 3) }
case 10000000000000000000000 { result := mul(uUNIT, 4) }
case 100000000000000000000000 { result := mul(uUNIT, 5) }
case 1000000000000000000000000 { result := mul(uUNIT, 6) }
case 10000000000000000000000000 { result := mul(uUNIT, 7) }
case 100000000000000000000000000 { result := mul(uUNIT, 8) }
case 1000000000000000000000000000 { result := mul(uUNIT, 9) }
case 10000000000000000000000000000 { result := mul(uUNIT, 10) }
case 100000000000000000000000000000 { result := mul(uUNIT, 11) }
case 1000000000000000000000000000000 { result := mul(uUNIT, 12) }
case 10000000000000000000000000000000 { result := mul(uUNIT, 13) }
case 100000000000000000000000000000000 { result := mul(uUNIT, 14) }
case 1000000000000000000000000000000000 { result := mul(uUNIT, 15) }
case 10000000000000000000000000000000000 { result := mul(uUNIT, 16) }
case 100000000000000000000000000000000000 { result := mul(uUNIT, 17) }
case 1000000000000000000000000000000000000 { result := mul(uUNIT, 18) }
case 10000000000000000000000000000000000000 { result := mul(uUNIT, 19) }
case 100000000000000000000000000000000000000 { result := mul(uUNIT, 20) }
case 1000000000000000000000000000000000000000 { result := mul(uUNIT, 21) }
case 10000000000000000000000000000000000000000 { result := mul(uUNIT, 22) }
case 100000000000000000000000000000000000000000 { result := mul(uUNIT, 23) }
case 1000000000000000000000000000000000000000000 { result := mul(uUNIT, 24) }
case 10000000000000000000000000000000000000000000 { result := mul(uUNIT, 25) }
case 100000000000000000000000000000000000000000000 { result := mul(uUNIT, 26) }
case 1000000000000000000000000000000000000000000000 { result := mul(uUNIT, 27) }
case 10000000000000000000000000000000000000000000000 { result := mul(uUNIT, 28) }
case 100000000000000000000000000000000000000000000000 { result := mul(uUNIT, 29) }
case 1000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 30) }
case 10000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 31) }
case 100000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 32) }
case 1000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 33) }
case 10000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 34) }
case 100000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 35) }
case 1000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 36) }
case 10000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 37) }
case 100000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 38) }
case 1000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 39) }
case 10000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 40) }
case 100000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 41) }
case 1000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 42) }
case 10000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 43) }
case 100000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 44) }
case 1000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 45) }
case 10000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 46) }
case 100000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 47) }
case 1000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 48) }
case 10000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 49) }
case 100000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 50) }
case 1000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 51) }
case 10000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 52) }
case 100000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 53) }
case 1000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 54) }
case 10000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 55) }
case 100000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 56) }
case 1000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 57) }
case 10000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 58) }
case 100000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 59) }
default { result := uMAX_UD60x18 }
}
if (result.unwrap() == uMAX_UD60x18) {
unchecked {
// Inline the fixed-point division to save gas.
result = wrap(log2(x).unwrap() * uUNIT / uLOG2_10);
}
}
}
/// @notice Calculates the binary logarithm of x using the iterative approximation algorithm:
///
/// $$
/// log_2{x} = n + log_2{y}, \text{ where } y = x*2^{-n}, \ y \in [1, 2)
/// $$
///
/// For $0 \leq x \lt 1$, the input is inverted:
///
/// $$
/// log_2{x} = -log_2{\frac{1}{x}}
/// $$
///
/// @dev See https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation
///
/// Notes:
/// - Due to the lossy precision of the iterative approximation, the results are not perfectly accurate to the last decimal.
///
/// Requirements:
/// - x must be greater than zero.
///
/// @param x The UD60x18 number for which to calculate the binary logarithm.
/// @return result The binary logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function log2(UD60x18 x) pure returns (UD60x18 result) {
uint256 xUint = x.unwrap();
if (xUint < uUNIT) {
revert Errors.PRBMath_UD60x18_Log_InputTooSmall(x);
}
unchecked {
// Calculate the integer part of the logarithm.
uint256 n = Common.msb(xUint / uUNIT);
// This is the integer part of the logarithm as a UD60x18 number. The operation can't overflow because n
// n is at most 255 and UNIT is 1e18.
uint256 resultUint = n * uUNIT;
// Calculate $y = x * 2^{-n}$.
uint256 y = xUint >> n;
// If y is the unit number, the fractional part is zero.
if (y == uUNIT) {
return wrap(resultUint);
}
// Calculate the fractional part via the iterative approximation.
// The `delta >>= 1` part is equivalent to `delta /= 2`, but shifting bits is more gas efficient.
uint256 DOUBLE_UNIT = 2e18;
for (uint256 delta = uHALF_UNIT; delta > 0; delta >>= 1) {
y = (y * y) / uUNIT;
// Is y^2 >= 2e18 and so in the range [2e18, 4e18)?
if (y >= DOUBLE_UNIT) {
// Add the 2^{-m} factor to the logarithm.
resultUint += delta;
// Halve y, which corresponds to z/2 in the Wikipedia article.
y >>= 1;
}
}
result = wrap(resultUint);
}
}
/// @notice Multiplies two UD60x18 numbers together, returning a new UD60x18 number.
///
/// @dev Uses {Common.mulDiv} to enable overflow-safe multiplication and division.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv}.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv}.
///
/// @dev See the documentation in {Common.mulDiv18}.
/// @param x The multiplicand as a UD60x18 number.
/// @param y The multiplier as a UD60x18 number.
/// @return result The product as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function mul(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
result = wrap(Common.mulDiv18(x.unwrap(), y.unwrap()));
}
/// @notice Raises x to the power of y.
///
/// For $1 \leq x \leq \infty$, the following standard formula is used:
///
/// $$
/// x^y = 2^{log_2{x} * y}
/// $$
///
/// For $0 \leq x \lt 1$, since the unsigned {log2} is undefined, an equivalent formula is used:
///
/// $$
/// i = \frac{1}{x}
/// w = 2^{log_2{i} * y}
/// x^y = \frac{1}{w}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {log2} and {mul}.
/// - Returns `UNIT` for 0^0.
/// - It may not perform well with very small values of x. Consider using SD59x18 as an alternative.
///
/// Requirements:
/// - Refer to the requirements in {exp2}, {log2}, and {mul}.
///
/// @param x The base as a UD60x18 number.
/// @param y The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function pow(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
uint256 xUint = x.unwrap();
uint256 yUint = y.unwrap();
// If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero.
if (xUint == 0) {
return yUint == 0 ? UNIT : ZERO;
}
// If x is `UNIT`, the result is always `UNIT`.
else if (xUint == uUNIT) {
return UNIT;
}
// If y is zero, the result is always `UNIT`.
if (yUint == 0) {
return UNIT;
}
// If y is `UNIT`, the result is always x.
else if (yUint == uUNIT) {
return x;
}
// If x is greater than `UNIT`, use the standard formula.
if (xUint > uUNIT) {
result = exp2(mul(log2(x), y));
}
// Conversely, if x is less than `UNIT`, use the equivalent formula.
else {
UD60x18 i = wrap(uUNIT_SQUARED / xUint);
UD60x18 w = exp2(mul(log2(i), y));
result = wrap(uUNIT_SQUARED / w.unwrap());
}
}
/// @notice Raises x (a UD60x18 number) to the power y (an unsigned basic integer) using the well-known
/// algorithm "exponentiation by squaring".
///
/// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv18}.
/// - Returns `UNIT` for 0^0.
///
/// Requirements:
/// - The result must fit in UD60x18.
///
/// @param x The base as a UD60x18 number.
/// @param y The exponent as a uint256.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function powu(UD60x18 x, uint256 y) pure returns (UD60x18 result) {
// Calculate the first iteration of the loop in advance.
uint256 xUint = x.unwrap();
uint256 resultUint = y & 1 > 0 ? xUint : uUNIT;
// Equivalent to `for(y /= 2; y > 0; y /= 2)`.
for (y >>= 1; y > 0; y >>= 1) {
xUint = Common.mulDiv18(xUint, xUint);
// Equivalent to `y % 2 == 1`.
if (y & 1 > 0) {
resultUint = Common.mulDiv18(resultUint, xUint);
}
}
result = wrap(resultUint);
}
/// @notice Calculates the square root of x using the Babylonian method.
///
/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x must be less than `MAX_UD60x18 / UNIT`.
///
/// @param x The UD60x18 number for which to calculate the square root.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function sqrt(UD60x18 x) pure returns (UD60x18 result) {
uint256 xUint = x.unwrap();
unchecked {
if (xUint > uMAX_UD60x18 / uUNIT) {
revert Errors.PRBMath_UD60x18_Sqrt_Overflow(x);
}
// Multiply x by `UNIT` to account for the factor of `UNIT` picked up when multiplying two UD60x18 numbers.
// In this case, the two numbers are both the square root.
result = wrap(Common.sqrt(xUint * uUNIT));
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { wrap } from "./Casting.sol";
import { UD60x18 } from "./ValueType.sol";
/// @notice Implements the checked addition operation (+) in the UD60x18 type.
function add(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
result = wrap(x.unwrap() + y.unwrap());
}
/// @notice Implements the AND (&) bitwise operation in the UD60x18 type.
function and(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
result = wrap(x.unwrap() & bits);
}
/// @notice Implements the AND (&) bitwise operation in the UD60x18 type.
function and2(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
result = wrap(x.unwrap() & y.unwrap());
}
/// @notice Implements the equal operation (==) in the UD60x18 type.
function eq(UD60x18 x, UD60x18 y) pure returns (bool result) {
result = x.unwrap() == y.unwrap();
}
/// @notice Implements the greater than operation (>) in the UD60x18 type.
function gt(UD60x18 x, UD60x18 y) pure returns (bool result) {
result = x.unwrap() > y.unwrap();
}
/// @notice Implements the greater than or equal to operation (>=) in the UD60x18 type.
function gte(UD60x18 x, UD60x18 y) pure returns (bool result) {
result = x.unwrap() >= y.unwrap();
}
/// @notice Implements a zero comparison check function in the UD60x18 type.
function isZero(UD60x18 x) pure returns (bool result) {
// This wouldn't work if x could be negative.
result = x.unwrap() == 0;
}
/// @notice Implements the left shift operation (<<) in the UD60x18 type.
function lshift(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
result = wrap(x.unwrap() << bits);
}
/// @notice Implements the lower than operation (<) in the UD60x18 type.
function lt(UD60x18 x, UD60x18 y) pure returns (bool result) {
result = x.unwrap() < y.unwrap();
}
/// @notice Implements the lower than or equal to operation (<=) in the UD60x18 type.
function lte(UD60x18 x, UD60x18 y) pure returns (bool result) {
result = x.unwrap() <= y.unwrap();
}
/// @notice Implements the checked modulo operation (%) in the UD60x18 type.
function mod(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
result = wrap(x.unwrap() % y.unwrap());
}
/// @notice Implements the not equal operation (!=) in the UD60x18 type.
function neq(UD60x18 x, UD60x18 y) pure returns (bool result) {
result = x.unwrap() != y.unwrap();
}
/// @notice Implements the NOT (~) bitwise operation in the UD60x18 type.
function not(UD60x18 x) pure returns (UD60x18 result) {
result = wrap(~x.unwrap());
}
/// @notice Implements the OR (|) bitwise operation in the UD60x18 type.
function or(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
result = wrap(x.unwrap() | y.unwrap());
}
/// @notice Implements the right shift operation (>>) in the UD60x18 type.
function rshift(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
result = wrap(x.unwrap() >> bits);
}
/// @notice Implements the checked subtraction operation (-) in the UD60x18 type.
function sub(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
result = wrap(x.unwrap() - y.unwrap());
}
/// @notice Implements the unchecked addition operation (+) in the UD60x18 type.
function uncheckedAdd(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
unchecked {
result = wrap(x.unwrap() + y.unwrap());
}
}
/// @notice Implements the unchecked subtraction operation (-) in the UD60x18 type.
function uncheckedSub(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
unchecked {
result = wrap(x.unwrap() - y.unwrap());
}
}
/// @notice Implements the XOR (^) bitwise operation in the UD60x18 type.
function xor(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
result = wrap(x.unwrap() ^ y.unwrap());
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { UD60x18 } from "./ValueType.sol";
/// @notice Thrown when ceiling a number overflows UD60x18.
error PRBMath_UD60x18_Ceil_Overflow(UD60x18 x);
/// @notice Thrown when converting a basic integer to the fixed-point format overflows UD60x18.
error PRBMath_UD60x18_Convert_Overflow(uint256 x);
/// @notice Thrown when taking the natural exponent of a base greater than 133_084258667509499441.
error PRBMath_UD60x18_Exp_InputTooBig(UD60x18 x);
/// @notice Thrown when taking the binary exponent of a base greater than 192e18.
error PRBMath_UD60x18_Exp2_InputTooBig(UD60x18 x);
/// @notice Thrown when taking the geometric mean of two numbers and multiplying them overflows UD60x18.
error PRBMath_UD60x18_Gm_Overflow(UD60x18 x, UD60x18 y);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD1x18.
error PRBMath_UD60x18_IntoSD1x18_Overflow(UD60x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD59x18.
error PRBMath_UD60x18_IntoSD59x18_Overflow(UD60x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD2x18.
error PRBMath_UD60x18_IntoUD2x18_Overflow(UD60x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint128.
error PRBMath_UD60x18_IntoUint128_Overflow(UD60x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint40.
error PRBMath_UD60x18_IntoUint40_Overflow(UD60x18 x);
/// @notice Thrown when taking the logarithm of a number less than 1.
error PRBMath_UD60x18_Log_InputTooSmall(UD60x18 x);
/// @notice Thrown when calculating the square root overflows UD60x18.
error PRBMath_UD60x18_Sqrt_Overflow(UD60x18 x);// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { uMAX_UD60x18, uUNIT } from "./Constants.sol";
import { PRBMath_UD60x18_Convert_Overflow } from "./Errors.sol";
import { UD60x18 } from "./ValueType.sol";
/// @notice Converts a UD60x18 number to a simple integer by dividing it by `UNIT`.
/// @dev The result is rounded toward zero.
/// @param x The UD60x18 number to convert.
/// @return result The same number in basic integer form.
function convert(UD60x18 x) pure returns (uint256 result) {
result = UD60x18.unwrap(x) / uUNIT;
}
/// @notice Converts a simple integer to UD60x18 by multiplying it by `UNIT`.
///
/// @dev Requirements:
/// - x must be less than or equal to `MAX_UD60x18 / UNIT`.
///
/// @param x The basic integer to convert.
/// @param result The same number converted to UD60x18.
function convert(uint256 x) pure returns (UD60x18 result) {
if (x > uMAX_UD60x18 / uUNIT) {
revert PRBMath_UD60x18_Convert_Overflow(x);
}
unchecked {
result = UD60x18.wrap(x * uUNIT);
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { UD60x18 } from "./ValueType.sol";
// NOTICE: the "u" prefix stands for "unwrapped".
/// @dev Euler's number as a UD60x18 number.
UD60x18 constant E = UD60x18.wrap(2_718281828459045235);
/// @dev The maximum input permitted in {exp}.
uint256 constant uEXP_MAX_INPUT = 133_084258667509499440;
UD60x18 constant EXP_MAX_INPUT = UD60x18.wrap(uEXP_MAX_INPUT);
/// @dev The maximum input permitted in {exp2}.
uint256 constant uEXP2_MAX_INPUT = 192e18 - 1;
UD60x18 constant EXP2_MAX_INPUT = UD60x18.wrap(uEXP2_MAX_INPUT);
/// @dev Half the UNIT number.
uint256 constant uHALF_UNIT = 0.5e18;
UD60x18 constant HALF_UNIT = UD60x18.wrap(uHALF_UNIT);
/// @dev $log_2(10)$ as a UD60x18 number.
uint256 constant uLOG2_10 = 3_321928094887362347;
UD60x18 constant LOG2_10 = UD60x18.wrap(uLOG2_10);
/// @dev $log_2(e)$ as a UD60x18 number.
uint256 constant uLOG2_E = 1_442695040888963407;
UD60x18 constant LOG2_E = UD60x18.wrap(uLOG2_E);
/// @dev The maximum value a UD60x18 number can have.
uint256 constant uMAX_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_584007913129639935;
UD60x18 constant MAX_UD60x18 = UD60x18.wrap(uMAX_UD60x18);
/// @dev The maximum whole value a UD60x18 number can have.
uint256 constant uMAX_WHOLE_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_000000000000000000;
UD60x18 constant MAX_WHOLE_UD60x18 = UD60x18.wrap(uMAX_WHOLE_UD60x18);
/// @dev PI as a UD60x18 number.
UD60x18 constant PI = UD60x18.wrap(3_141592653589793238);
/// @dev The unit number, which gives the decimal precision of UD60x18.
uint256 constant uUNIT = 1e18;
UD60x18 constant UNIT = UD60x18.wrap(uUNIT);
/// @dev The unit number squared.
uint256 constant uUNIT_SQUARED = 1e36;
UD60x18 constant UNIT_SQUARED = UD60x18.wrap(uUNIT_SQUARED);
/// @dev Zero as a UD60x18 number.
UD60x18 constant ZERO = UD60x18.wrap(0);// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import "./Errors.sol" as CastingErrors;
import { MAX_UINT128, MAX_UINT40 } from "../Common.sol";
import { uMAX_SD1x18 } from "../sd1x18/Constants.sol";
import { SD1x18 } from "../sd1x18/ValueType.sol";
import { uMAX_SD59x18 } from "../sd59x18/Constants.sol";
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { uMAX_UD2x18 } from "../ud2x18/Constants.sol";
import { UD2x18 } from "../ud2x18/ValueType.sol";
import { UD60x18 } from "./ValueType.sol";
/// @notice Casts a UD60x18 number into SD1x18.
/// @dev Requirements:
/// - x must be less than or equal to `uMAX_SD1x18`.
function intoSD1x18(UD60x18 x) pure returns (SD1x18 result) {
uint256 xUint = UD60x18.unwrap(x);
if (xUint > uint256(int256(uMAX_SD1x18))) {
revert CastingErrors.PRBMath_UD60x18_IntoSD1x18_Overflow(x);
}
result = SD1x18.wrap(int64(uint64(xUint)));
}
/// @notice Casts a UD60x18 number into UD2x18.
/// @dev Requirements:
/// - x must be less than or equal to `uMAX_UD2x18`.
function intoUD2x18(UD60x18 x) pure returns (UD2x18 result) {
uint256 xUint = UD60x18.unwrap(x);
if (xUint > uMAX_UD2x18) {
revert CastingErrors.PRBMath_UD60x18_IntoUD2x18_Overflow(x);
}
result = UD2x18.wrap(uint64(xUint));
}
/// @notice Casts a UD60x18 number into SD59x18.
/// @dev Requirements:
/// - x must be less than or equal to `uMAX_SD59x18`.
function intoSD59x18(UD60x18 x) pure returns (SD59x18 result) {
uint256 xUint = UD60x18.unwrap(x);
if (xUint > uint256(uMAX_SD59x18)) {
revert CastingErrors.PRBMath_UD60x18_IntoSD59x18_Overflow(x);
}
result = SD59x18.wrap(int256(xUint));
}
/// @notice Casts a UD60x18 number into uint128.
/// @dev This is basically an alias for {unwrap}.
function intoUint256(UD60x18 x) pure returns (uint256 result) {
result = UD60x18.unwrap(x);
}
/// @notice Casts a UD60x18 number into uint128.
/// @dev Requirements:
/// - x must be less than or equal to `MAX_UINT128`.
function intoUint128(UD60x18 x) pure returns (uint128 result) {
uint256 xUint = UD60x18.unwrap(x);
if (xUint > MAX_UINT128) {
revert CastingErrors.PRBMath_UD60x18_IntoUint128_Overflow(x);
}
result = uint128(xUint);
}
/// @notice Casts a UD60x18 number into uint40.
/// @dev Requirements:
/// - x must be less than or equal to `MAX_UINT40`.
function intoUint40(UD60x18 x) pure returns (uint40 result) {
uint256 xUint = UD60x18.unwrap(x);
if (xUint > MAX_UINT40) {
revert CastingErrors.PRBMath_UD60x18_IntoUint40_Overflow(x);
}
result = uint40(xUint);
}
/// @notice Alias for {wrap}.
function ud(uint256 x) pure returns (UD60x18 result) {
result = UD60x18.wrap(x);
}
/// @notice Alias for {wrap}.
function ud60x18(uint256 x) pure returns (UD60x18 result) {
result = UD60x18.wrap(x);
}
/// @notice Unwraps a UD60x18 number into uint256.
function unwrap(UD60x18 x) pure returns (uint256 result) {
result = UD60x18.unwrap(x);
}
/// @notice Wraps a uint256 number into the UD60x18 value type.
function wrap(uint256 x) pure returns (UD60x18 result) {
result = UD60x18.wrap(x);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
// Common.sol
//
// Common mathematical functions used in both SD59x18 and UD60x18. Note that these global functions do not
// always operate with SD59x18 and UD60x18 numbers.
/*//////////////////////////////////////////////////////////////////////////
CUSTOM ERRORS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Thrown when the resultant value in {mulDiv} overflows uint256.
error PRBMath_MulDiv_Overflow(uint256 x, uint256 y, uint256 denominator);
/// @notice Thrown when the resultant value in {mulDiv18} overflows uint256.
error PRBMath_MulDiv18_Overflow(uint256 x, uint256 y);
/// @notice Thrown when one of the inputs passed to {mulDivSigned} is `type(int256).min`.
error PRBMath_MulDivSigned_InputTooSmall();
/// @notice Thrown when the resultant value in {mulDivSigned} overflows int256.
error PRBMath_MulDivSigned_Overflow(int256 x, int256 y);
/*//////////////////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////////////////*/
/// @dev The maximum value a uint128 number can have.
uint128 constant MAX_UINT128 = type(uint128).max;
/// @dev The maximum value a uint40 number can have.
uint40 constant MAX_UINT40 = type(uint40).max;
/// @dev The unit number, which the decimal precision of the fixed-point types.
uint256 constant UNIT = 1e18;
/// @dev The unit number inverted mod 2^256.
uint256 constant UNIT_INVERSE = 78156646155174841979727994598816262306175212592076161876661_508869554232690281;
/// @dev The the largest power of two that divides the decimal value of `UNIT`. The logarithm of this value is the least significant
/// bit in the binary representation of `UNIT`.
uint256 constant UNIT_LPOTD = 262144;
/*//////////////////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Calculates the binary exponent of x using the binary fraction method.
/// @dev Has to use 192.64-bit fixed-point numbers. See https://ethereum.stackexchange.com/a/96594/24693.
/// @param x The exponent as an unsigned 192.64-bit fixed-point number.
/// @return result The result as an unsigned 60.18-decimal fixed-point number.
/// @custom:smtchecker abstract-function-nondet
function exp2(uint256 x) pure returns (uint256 result) {
unchecked {
// Start from 0.5 in the 192.64-bit fixed-point format.
result = 0x800000000000000000000000000000000000000000000000;
// The following logic multiplies the result by $\sqrt{2^{-i}}$ when the bit at position i is 1. Key points:
//
// 1. Intermediate results will not overflow, as the starting point is 2^191 and all magic factors are under 2^65.
// 2. The rationale for organizing the if statements into groups of 8 is gas savings. If the result of performing
// a bitwise AND operation between x and any value in the array [0x80; 0x40; 0x20; 0x10; 0x08; 0x04; 0x02; 0x01] is 1,
// we know that `x & 0xFF` is also 1.
if (x & 0xFF00000000000000 > 0) {
if (x & 0x8000000000000000 > 0) {
result = (result * 0x16A09E667F3BCC909) >> 64;
}
if (x & 0x4000000000000000 > 0) {
result = (result * 0x1306FE0A31B7152DF) >> 64;
}
if (x & 0x2000000000000000 > 0) {
result = (result * 0x1172B83C7D517ADCE) >> 64;
}
if (x & 0x1000000000000000 > 0) {
result = (result * 0x10B5586CF9890F62A) >> 64;
}
if (x & 0x800000000000000 > 0) {
result = (result * 0x1059B0D31585743AE) >> 64;
}
if (x & 0x400000000000000 > 0) {
result = (result * 0x102C9A3E778060EE7) >> 64;
}
if (x & 0x200000000000000 > 0) {
result = (result * 0x10163DA9FB33356D8) >> 64;
}
if (x & 0x100000000000000 > 0) {
result = (result * 0x100B1AFA5ABCBED61) >> 64;
}
}
if (x & 0xFF000000000000 > 0) {
if (x & 0x80000000000000 > 0) {
result = (result * 0x10058C86DA1C09EA2) >> 64;
}
if (x & 0x40000000000000 > 0) {
result = (result * 0x1002C605E2E8CEC50) >> 64;
}
if (x & 0x20000000000000 > 0) {
result = (result * 0x100162F3904051FA1) >> 64;
}
if (x & 0x10000000000000 > 0) {
result = (result * 0x1000B175EFFDC76BA) >> 64;
}
if (x & 0x8000000000000 > 0) {
result = (result * 0x100058BA01FB9F96D) >> 64;
}
if (x & 0x4000000000000 > 0) {
result = (result * 0x10002C5CC37DA9492) >> 64;
}
if (x & 0x2000000000000 > 0) {
result = (result * 0x1000162E525EE0547) >> 64;
}
if (x & 0x1000000000000 > 0) {
result = (result * 0x10000B17255775C04) >> 64;
}
}
if (x & 0xFF0000000000 > 0) {
if (x & 0x800000000000 > 0) {
result = (result * 0x1000058B91B5BC9AE) >> 64;
}
if (x & 0x400000000000 > 0) {
result = (result * 0x100002C5C89D5EC6D) >> 64;
}
if (x & 0x200000000000 > 0) {
result = (result * 0x10000162E43F4F831) >> 64;
}
if (x & 0x100000000000 > 0) {
result = (result * 0x100000B1721BCFC9A) >> 64;
}
if (x & 0x80000000000 > 0) {
result = (result * 0x10000058B90CF1E6E) >> 64;
}
if (x & 0x40000000000 > 0) {
result = (result * 0x1000002C5C863B73F) >> 64;
}
if (x & 0x20000000000 > 0) {
result = (result * 0x100000162E430E5A2) >> 64;
}
if (x & 0x10000000000 > 0) {
result = (result * 0x1000000B172183551) >> 64;
}
}
if (x & 0xFF00000000 > 0) {
if (x & 0x8000000000 > 0) {
result = (result * 0x100000058B90C0B49) >> 64;
}
if (x & 0x4000000000 > 0) {
result = (result * 0x10000002C5C8601CC) >> 64;
}
if (x & 0x2000000000 > 0) {
result = (result * 0x1000000162E42FFF0) >> 64;
}
if (x & 0x1000000000 > 0) {
result = (result * 0x10000000B17217FBB) >> 64;
}
if (x & 0x800000000 > 0) {
result = (result * 0x1000000058B90BFCE) >> 64;
}
if (x & 0x400000000 > 0) {
result = (result * 0x100000002C5C85FE3) >> 64;
}
if (x & 0x200000000 > 0) {
result = (result * 0x10000000162E42FF1) >> 64;
}
if (x & 0x100000000 > 0) {
result = (result * 0x100000000B17217F8) >> 64;
}
}
if (x & 0xFF000000 > 0) {
if (x & 0x80000000 > 0) {
result = (result * 0x10000000058B90BFC) >> 64;
}
if (x & 0x40000000 > 0) {
result = (result * 0x1000000002C5C85FE) >> 64;
}
if (x & 0x20000000 > 0) {
result = (result * 0x100000000162E42FF) >> 64;
}
if (x & 0x10000000 > 0) {
result = (result * 0x1000000000B17217F) >> 64;
}
if (x & 0x8000000 > 0) {
result = (result * 0x100000000058B90C0) >> 64;
}
if (x & 0x4000000 > 0) {
result = (result * 0x10000000002C5C860) >> 64;
}
if (x & 0x2000000 > 0) {
result = (result * 0x1000000000162E430) >> 64;
}
if (x & 0x1000000 > 0) {
result = (result * 0x10000000000B17218) >> 64;
}
}
if (x & 0xFF0000 > 0) {
if (x & 0x800000 > 0) {
result = (result * 0x1000000000058B90C) >> 64;
}
if (x & 0x400000 > 0) {
result = (result * 0x100000000002C5C86) >> 64;
}
if (x & 0x200000 > 0) {
result = (result * 0x10000000000162E43) >> 64;
}
if (x & 0x100000 > 0) {
result = (result * 0x100000000000B1721) >> 64;
}
if (x & 0x80000 > 0) {
result = (result * 0x10000000000058B91) >> 64;
}
if (x & 0x40000 > 0) {
result = (result * 0x1000000000002C5C8) >> 64;
}
if (x & 0x20000 > 0) {
result = (result * 0x100000000000162E4) >> 64;
}
if (x & 0x10000 > 0) {
result = (result * 0x1000000000000B172) >> 64;
}
}
if (x & 0xFF00 > 0) {
if (x & 0x8000 > 0) {
result = (result * 0x100000000000058B9) >> 64;
}
if (x & 0x4000 > 0) {
result = (result * 0x10000000000002C5D) >> 64;
}
if (x & 0x2000 > 0) {
result = (result * 0x1000000000000162E) >> 64;
}
if (x & 0x1000 > 0) {
result = (result * 0x10000000000000B17) >> 64;
}
if (x & 0x800 > 0) {
result = (result * 0x1000000000000058C) >> 64;
}
if (x & 0x400 > 0) {
result = (result * 0x100000000000002C6) >> 64;
}
if (x & 0x200 > 0) {
result = (result * 0x10000000000000163) >> 64;
}
if (x & 0x100 > 0) {
result = (result * 0x100000000000000B1) >> 64;
}
}
if (x & 0xFF > 0) {
if (x & 0x80 > 0) {
result = (result * 0x10000000000000059) >> 64;
}
if (x & 0x40 > 0) {
result = (result * 0x1000000000000002C) >> 64;
}
if (x & 0x20 > 0) {
result = (result * 0x10000000000000016) >> 64;
}
if (x & 0x10 > 0) {
result = (result * 0x1000000000000000B) >> 64;
}
if (x & 0x8 > 0) {
result = (result * 0x10000000000000006) >> 64;
}
if (x & 0x4 > 0) {
result = (result * 0x10000000000000003) >> 64;
}
if (x & 0x2 > 0) {
result = (result * 0x10000000000000001) >> 64;
}
if (x & 0x1 > 0) {
result = (result * 0x10000000000000001) >> 64;
}
}
// In the code snippet below, two operations are executed simultaneously:
//
// 1. The result is multiplied by $(2^n + 1)$, where $2^n$ represents the integer part, and the additional 1
// accounts for the initial guess of 0.5. This is achieved by subtracting from 191 instead of 192.
// 2. The result is then converted to an unsigned 60.18-decimal fixed-point format.
//
// The underlying logic is based on the relationship $2^{191-ip} = 2^{ip} / 2^{191}$, where $ip$ denotes the,
// integer part, $2^n$.
result *= UNIT;
result >>= (191 - (x >> 64));
}
}
/// @notice Finds the zero-based index of the first 1 in the binary representation of x.
///
/// @dev See the note on "msb" in this Wikipedia article: https://en.wikipedia.org/wiki/Find_first_set
///
/// Each step in this implementation is equivalent to this high-level code:
///
/// ```solidity
/// if (x >= 2 ** 128) {
/// x >>= 128;
/// result += 128;
/// }
/// ```
///
/// Where 128 is replaced with each respective power of two factor. See the full high-level implementation here:
/// https://gist.github.com/PaulRBerg/f932f8693f2733e30c4d479e8e980948
///
/// The Yul instructions used below are:
///
/// - "gt" is "greater than"
/// - "or" is the OR bitwise operator
/// - "shl" is "shift left"
/// - "shr" is "shift right"
///
/// @param x The uint256 number for which to find the index of the most significant bit.
/// @return result The index of the most significant bit as a uint256.
/// @custom:smtchecker abstract-function-nondet
function msb(uint256 x) pure returns (uint256 result) {
// 2^128
assembly ("memory-safe") {
let factor := shl(7, gt(x, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^64
assembly ("memory-safe") {
let factor := shl(6, gt(x, 0xFFFFFFFFFFFFFFFF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^32
assembly ("memory-safe") {
let factor := shl(5, gt(x, 0xFFFFFFFF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^16
assembly ("memory-safe") {
let factor := shl(4, gt(x, 0xFFFF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^8
assembly ("memory-safe") {
let factor := shl(3, gt(x, 0xFF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^4
assembly ("memory-safe") {
let factor := shl(2, gt(x, 0xF))
x := shr(factor, x)
result := or(result, factor)
}
// 2^2
assembly ("memory-safe") {
let factor := shl(1, gt(x, 0x3))
x := shr(factor, x)
result := or(result, factor)
}
// 2^1
// No need to shift x any more.
assembly ("memory-safe") {
let factor := gt(x, 0x1)
result := or(result, factor)
}
}
/// @notice Calculates x*y÷denominator with 512-bit precision.
///
/// @dev Credits to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - The denominator must not be zero.
/// - The result must fit in uint256.
///
/// @param x The multiplicand as a uint256.
/// @param y The multiplier as a uint256.
/// @param denominator The divisor as a uint256.
/// @return result The result as a uint256.
/// @custom:smtchecker abstract-function-nondet
function mulDiv(uint256 x, uint256 y, uint256 denominator) pure returns (uint256 result) {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512-bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly ("memory-safe") {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
unchecked {
return prod0 / denominator;
}
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (prod1 >= denominator) {
revert PRBMath_MulDiv_Overflow(x, y, denominator);
}
////////////////////////////////////////////////////////////////////////////
// 512 by 256 division
////////////////////////////////////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly ("memory-safe") {
// Compute remainder using the mulmod Yul instruction.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512-bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
unchecked {
// Calculate the largest power of two divisor of the denominator using the unary operator ~. This operation cannot overflow
// because the denominator cannot be zero at this point in the function execution. The result is always >= 1.
// For more detail, see https://cs.stackexchange.com/q/138556/92363.
uint256 lpotdod = denominator & (~denominator + 1);
uint256 flippedLpotdod;
assembly ("memory-safe") {
// Factor powers of two out of denominator.
denominator := div(denominator, lpotdod)
// Divide [prod1 prod0] by lpotdod.
prod0 := div(prod0, lpotdod)
// Get the flipped value `2^256 / lpotdod`. If the `lpotdod` is zero, the flipped value is one.
// `sub(0, lpotdod)` produces the two's complement version of `lpotdod`, which is equivalent to flipping all the bits.
// However, `div` interprets this value as an unsigned value: https://ethereum.stackexchange.com/q/147168/24693
flippedLpotdod := add(div(sub(0, lpotdod), lpotdod), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * flippedLpotdod;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
}
}
/// @notice Calculates x*y÷1e18 with 512-bit precision.
///
/// @dev A variant of {mulDiv} with constant folding, i.e. in which the denominator is hard coded to 1e18.
///
/// Notes:
/// - The body is purposely left uncommented; to understand how this works, see the documentation in {mulDiv}.
/// - The result is rounded toward zero.
/// - We take as an axiom that the result cannot be `MAX_UINT256` when x and y solve the following system of equations:
///
/// $$
/// \begin{cases}
/// x * y = MAX\_UINT256 * UNIT \\
/// (x * y) \% UNIT \geq \frac{UNIT}{2}
/// \end{cases}
/// $$
///
/// Requirements:
/// - Refer to the requirements in {mulDiv}.
/// - The result must fit in uint256.
///
/// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number.
/// @param y The multiplier as an unsigned 60.18-decimal fixed-point number.
/// @return result The result as an unsigned 60.18-decimal fixed-point number.
/// @custom:smtchecker abstract-function-nondet
function mulDiv18(uint256 x, uint256 y) pure returns (uint256 result) {
uint256 prod0;
uint256 prod1;
assembly ("memory-safe") {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
if (prod1 == 0) {
unchecked {
return prod0 / UNIT;
}
}
if (prod1 >= UNIT) {
revert PRBMath_MulDiv18_Overflow(x, y);
}
uint256 remainder;
assembly ("memory-safe") {
remainder := mulmod(x, y, UNIT)
result :=
mul(
or(
div(sub(prod0, remainder), UNIT_LPOTD),
mul(sub(prod1, gt(remainder, prod0)), add(div(sub(0, UNIT_LPOTD), UNIT_LPOTD), 1))
),
UNIT_INVERSE
)
}
}
/// @notice Calculates x*y÷denominator with 512-bit precision.
///
/// @dev This is an extension of {mulDiv} for signed numbers, which works by computing the signs and the absolute values separately.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - Refer to the requirements in {mulDiv}.
/// - None of the inputs can be `type(int256).min`.
/// - The result must fit in int256.
///
/// @param x The multiplicand as an int256.
/// @param y The multiplier as an int256.
/// @param denominator The divisor as an int256.
/// @return result The result as an int256.
/// @custom:smtchecker abstract-function-nondet
function mulDivSigned(int256 x, int256 y, int256 denominator) pure returns (int256 result) {
if (x == type(int256).min || y == type(int256).min || denominator == type(int256).min) {
revert PRBMath_MulDivSigned_InputTooSmall();
}
// Get hold of the absolute values of x, y and the denominator.
uint256 xAbs;
uint256 yAbs;
uint256 dAbs;
unchecked {
xAbs = x < 0 ? uint256(-x) : uint256(x);
yAbs = y < 0 ? uint256(-y) : uint256(y);
dAbs = denominator < 0 ? uint256(-denominator) : uint256(denominator);
}
// Compute the absolute value of x*y÷denominator. The result must fit in int256.
uint256 resultAbs = mulDiv(xAbs, yAbs, dAbs);
if (resultAbs > uint256(type(int256).max)) {
revert PRBMath_MulDivSigned_Overflow(x, y);
}
// Get the signs of x, y and the denominator.
uint256 sx;
uint256 sy;
uint256 sd;
assembly ("memory-safe") {
// "sgt" is the "signed greater than" assembly instruction and "sub(0,1)" is -1 in two's complement.
sx := sgt(x, sub(0, 1))
sy := sgt(y, sub(0, 1))
sd := sgt(denominator, sub(0, 1))
}
// XOR over sx, sy and sd. What this does is to check whether there are 1 or 3 negative signs in the inputs.
// If there are, the result should be negative. Otherwise, it should be positive.
unchecked {
result = sx ^ sy ^ sd == 0 ? -int256(resultAbs) : int256(resultAbs);
}
}
/// @notice Calculates the square root of x using the Babylonian method.
///
/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
///
/// Notes:
/// - If x is not a perfect square, the result is rounded down.
/// - Credits to OpenZeppelin for the explanations in comments below.
///
/// @param x The uint256 number for which to calculate the square root.
/// @return result The result as a uint256.
/// @custom:smtchecker abstract-function-nondet
function sqrt(uint256 x) pure returns (uint256 result) {
if (x == 0) {
return 0;
}
// For our first guess, we calculate the biggest power of 2 which is smaller than the square root of x.
//
// We know that the "msb" (most significant bit) of x is a power of 2 such that we have:
//
// $$
// msb(x) <= x <= 2*msb(x)$
// $$
//
// We write $msb(x)$ as $2^k$, and we get:
//
// $$
// k = log_2(x)
// $$
//
// Thus, we can write the initial inequality as:
//
// $$
// 2^{log_2(x)} <= x <= 2*2^{log_2(x)+1} \\
// sqrt(2^k) <= sqrt(x) < sqrt(2^{k+1}) \\
// 2^{k/2} <= sqrt(x) < 2^{(k+1)/2} <= 2^{(k/2)+1}
// $$
//
// Consequently, $2^{log_2(x) /2} is a good first approximation of sqrt(x) with at least one correct bit.
uint256 xAux = uint256(x);
result = 1;
if (xAux >= 2 ** 128) {
xAux >>= 128;
result <<= 64;
}
if (xAux >= 2 ** 64) {
xAux >>= 64;
result <<= 32;
}
if (xAux >= 2 ** 32) {
xAux >>= 32;
result <<= 16;
}
if (xAux >= 2 ** 16) {
xAux >>= 16;
result <<= 8;
}
if (xAux >= 2 ** 8) {
xAux >>= 8;
result <<= 4;
}
if (xAux >= 2 ** 4) {
xAux >>= 4;
result <<= 2;
}
if (xAux >= 2 ** 2) {
result <<= 1;
}
// At this point, `result` is an estimation with at least one bit of precision. We know the true value has at
// most 128 bits, since it is the square root of a uint256. Newton's method converges quadratically (precision
// doubles at every iteration). We thus need at most 7 iteration to turn our partial result with one bit of
// precision into the expected uint128 result.
unchecked {
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
result = (result + x / result) >> 1;
// If x is not a perfect square, round the result toward zero.
uint256 roundedResult = x / result;
if (result >= roundedResult) {
result = roundedResult;
}
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import "./Casting.sol" as Casting;
/// @notice The unsigned 2.18-decimal fixed-point number representation, which can have up to 2 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type uint64. This is useful when end users want to use uint64 to save gas, e.g. with tight variable packing in contract
/// storage.
type UD2x18 is uint64;
/*//////////////////////////////////////////////////////////////////////////
CASTING
//////////////////////////////////////////////////////////////////////////*/
using {
Casting.intoSD1x18,
Casting.intoSD59x18,
Casting.intoUD60x18,
Casting.intoUint256,
Casting.intoUint128,
Casting.intoUint40,
Casting.unwrap
} for UD2x18 global;// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { UD2x18 } from "./ValueType.sol";
/// @dev Euler's number as a UD2x18 number.
UD2x18 constant E = UD2x18.wrap(2_718281828459045235);
/// @dev The maximum value a UD2x18 number can have.
uint64 constant uMAX_UD2x18 = 18_446744073709551615;
UD2x18 constant MAX_UD2x18 = UD2x18.wrap(uMAX_UD2x18);
/// @dev PI as a UD2x18 number.
UD2x18 constant PI = UD2x18.wrap(3_141592653589793238);
/// @dev The unit number, which gives the decimal precision of UD2x18.
UD2x18 constant UNIT = UD2x18.wrap(1e18);
uint64 constant uUNIT = 1e18;// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import "./Casting.sol" as Casting;
import "./Helpers.sol" as Helpers;
import "./Math.sol" as Math;
/// @notice The signed 59.18-decimal fixed-point number representation, which can have up to 59 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type int256.
type SD59x18 is int256;
/*//////////////////////////////////////////////////////////////////////////
CASTING
//////////////////////////////////////////////////////////////////////////*/
using {
Casting.intoInt256,
Casting.intoSD1x18,
Casting.intoUD2x18,
Casting.intoUD60x18,
Casting.intoUint256,
Casting.intoUint128,
Casting.intoUint40,
Casting.unwrap
} for SD59x18 global;
/*//////////////////////////////////////////////////////////////////////////
MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
using {
Math.abs,
Math.avg,
Math.ceil,
Math.div,
Math.exp,
Math.exp2,
Math.floor,
Math.frac,
Math.gm,
Math.inv,
Math.log10,
Math.log2,
Math.ln,
Math.mul,
Math.pow,
Math.powu,
Math.sqrt
} for SD59x18 global;
/*//////////////////////////////////////////////////////////////////////////
HELPER FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
using {
Helpers.add,
Helpers.and,
Helpers.eq,
Helpers.gt,
Helpers.gte,
Helpers.isZero,
Helpers.lshift,
Helpers.lt,
Helpers.lte,
Helpers.mod,
Helpers.neq,
Helpers.not,
Helpers.or,
Helpers.rshift,
Helpers.sub,
Helpers.uncheckedAdd,
Helpers.uncheckedSub,
Helpers.uncheckedUnary,
Helpers.xor
} for SD59x18 global;
/*//////////////////////////////////////////////////////////////////////////
OPERATORS
//////////////////////////////////////////////////////////////////////////*/
// The global "using for" directive makes it possible to use these operators on the SD59x18 type.
using {
Helpers.add as +,
Helpers.and2 as &,
Math.div as /,
Helpers.eq as ==,
Helpers.gt as >,
Helpers.gte as >=,
Helpers.lt as <,
Helpers.lte as <=,
Helpers.mod as %,
Math.mul as *,
Helpers.neq as !=,
Helpers.not as ~,
Helpers.or as |,
Helpers.sub as -,
Helpers.unary as -,
Helpers.xor as ^
} for SD59x18 global;// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { SD59x18 } from "./ValueType.sol";
// NOTICE: the "u" prefix stands for "unwrapped".
/// @dev Euler's number as an SD59x18 number.
SD59x18 constant E = SD59x18.wrap(2_718281828459045235);
/// @dev The maximum input permitted in {exp}.
int256 constant uEXP_MAX_INPUT = 133_084258667509499440;
SD59x18 constant EXP_MAX_INPUT = SD59x18.wrap(uEXP_MAX_INPUT);
/// @dev Any value less than this returns 0 in {exp}.
int256 constant uEXP_MIN_THRESHOLD = -41_446531673892822322;
SD59x18 constant EXP_MIN_THRESHOLD = SD59x18.wrap(uEXP_MIN_THRESHOLD);
/// @dev The maximum input permitted in {exp2}.
int256 constant uEXP2_MAX_INPUT = 192e18 - 1;
SD59x18 constant EXP2_MAX_INPUT = SD59x18.wrap(uEXP2_MAX_INPUT);
/// @dev Any value less than this returns 0 in {exp2}.
int256 constant uEXP2_MIN_THRESHOLD = -59_794705707972522261;
SD59x18 constant EXP2_MIN_THRESHOLD = SD59x18.wrap(uEXP2_MIN_THRESHOLD);
/// @dev Half the UNIT number.
int256 constant uHALF_UNIT = 0.5e18;
SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT);
/// @dev $log_2(10)$ as an SD59x18 number.
int256 constant uLOG2_10 = 3_321928094887362347;
SD59x18 constant LOG2_10 = SD59x18.wrap(uLOG2_10);
/// @dev $log_2(e)$ as an SD59x18 number.
int256 constant uLOG2_E = 1_442695040888963407;
SD59x18 constant LOG2_E = SD59x18.wrap(uLOG2_E);
/// @dev The maximum value an SD59x18 number can have.
int256 constant uMAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967;
SD59x18 constant MAX_SD59x18 = SD59x18.wrap(uMAX_SD59x18);
/// @dev The maximum whole value an SD59x18 number can have.
int256 constant uMAX_WHOLE_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_000000000000000000;
SD59x18 constant MAX_WHOLE_SD59x18 = SD59x18.wrap(uMAX_WHOLE_SD59x18);
/// @dev The minimum value an SD59x18 number can have.
int256 constant uMIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968;
SD59x18 constant MIN_SD59x18 = SD59x18.wrap(uMIN_SD59x18);
/// @dev The minimum whole value an SD59x18 number can have.
int256 constant uMIN_WHOLE_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_000000000000000000;
SD59x18 constant MIN_WHOLE_SD59x18 = SD59x18.wrap(uMIN_WHOLE_SD59x18);
/// @dev PI as an SD59x18 number.
SD59x18 constant PI = SD59x18.wrap(3_141592653589793238);
/// @dev The unit number, which gives the decimal precision of SD59x18.
int256 constant uUNIT = 1e18;
SD59x18 constant UNIT = SD59x18.wrap(1e18);
/// @dev The unit number squared.
int256 constant uUNIT_SQUARED = 1e36;
SD59x18 constant UNIT_SQUARED = SD59x18.wrap(uUNIT_SQUARED);
/// @dev Zero as an SD59x18 number.
SD59x18 constant ZERO = SD59x18.wrap(0);// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import "./Casting.sol" as Casting;
/// @notice The signed 1.18-decimal fixed-point number representation, which can have up to 1 digit and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type int64. This is useful when end users want to use int64 to save gas, e.g. with tight variable packing in contract
/// storage.
type SD1x18 is int64;
/*//////////////////////////////////////////////////////////////////////////
CASTING
//////////////////////////////////////////////////////////////////////////*/
using {
Casting.intoSD59x18,
Casting.intoUD2x18,
Casting.intoUD60x18,
Casting.intoUint256,
Casting.intoUint128,
Casting.intoUint40,
Casting.unwrap
} for SD1x18 global;// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { SD1x18 } from "./ValueType.sol";
/// @dev Euler's number as an SD1x18 number.
SD1x18 constant E = SD1x18.wrap(2_718281828459045235);
/// @dev The maximum value an SD1x18 number can have.
int64 constant uMAX_SD1x18 = 9_223372036854775807;
SD1x18 constant MAX_SD1x18 = SD1x18.wrap(uMAX_SD1x18);
/// @dev The maximum value an SD1x18 number can have.
int64 constant uMIN_SD1x18 = -9_223372036854775808;
SD1x18 constant MIN_SD1x18 = SD1x18.wrap(uMIN_SD1x18);
/// @dev PI as an SD1x18 number.
SD1x18 constant PI = SD1x18.wrap(3_141592653589793238);
/// @dev The unit number, which gives the decimal precision of SD1x18.
SD1x18 constant UNIT = SD1x18.wrap(1e18);
int64 constant uUNIT = 1e18;// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import { uMAX_SD1x18 } from "../sd1x18/Constants.sol";
import { SD1x18 } from "../sd1x18/ValueType.sol";
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { UD2x18 } from "./ValueType.sol";
/// @notice Casts a UD2x18 number into SD1x18.
/// - x must be less than or equal to `uMAX_SD1x18`.
function intoSD1x18(UD2x18 x) pure returns (SD1x18 result) {
uint64 xUint = UD2x18.unwrap(x);
if (xUint > uint64(uMAX_SD1x18)) {
revert Errors.PRBMath_UD2x18_IntoSD1x18_Overflow(x);
}
result = SD1x18.wrap(int64(xUint));
}
/// @notice Casts a UD2x18 number into SD59x18.
/// @dev There is no overflow check because the domain of UD2x18 is a subset of SD59x18.
function intoSD59x18(UD2x18 x) pure returns (SD59x18 result) {
result = SD59x18.wrap(int256(uint256(UD2x18.unwrap(x))));
}
/// @notice Casts a UD2x18 number into UD60x18.
/// @dev There is no overflow check because the domain of UD2x18 is a subset of UD60x18.
function intoUD60x18(UD2x18 x) pure returns (UD60x18 result) {
result = UD60x18.wrap(UD2x18.unwrap(x));
}
/// @notice Casts a UD2x18 number into uint128.
/// @dev There is no overflow check because the domain of UD2x18 is a subset of uint128.
function intoUint128(UD2x18 x) pure returns (uint128 result) {
result = uint128(UD2x18.unwrap(x));
}
/// @notice Casts a UD2x18 number into uint256.
/// @dev There is no overflow check because the domain of UD2x18 is a subset of uint256.
function intoUint256(UD2x18 x) pure returns (uint256 result) {
result = uint256(UD2x18.unwrap(x));
}
/// @notice Casts a UD2x18 number into uint40.
/// @dev Requirements:
/// - x must be less than or equal to `MAX_UINT40`.
function intoUint40(UD2x18 x) pure returns (uint40 result) {
uint64 xUint = UD2x18.unwrap(x);
if (xUint > uint64(Common.MAX_UINT40)) {
revert Errors.PRBMath_UD2x18_IntoUint40_Overflow(x);
}
result = uint40(xUint);
}
/// @notice Alias for {wrap}.
function ud2x18(uint64 x) pure returns (UD2x18 result) {
result = UD2x18.wrap(x);
}
/// @notice Unwrap a UD2x18 number into uint64.
function unwrap(UD2x18 x) pure returns (uint64 result) {
result = UD2x18.unwrap(x);
}
/// @notice Wraps a uint64 number into UD2x18.
function wrap(uint64 x) pure returns (UD2x18 result) {
result = UD2x18.wrap(x);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import {
uEXP_MAX_INPUT,
uEXP2_MAX_INPUT,
uEXP_MIN_THRESHOLD,
uEXP2_MIN_THRESHOLD,
uHALF_UNIT,
uLOG2_10,
uLOG2_E,
uMAX_SD59x18,
uMAX_WHOLE_SD59x18,
uMIN_SD59x18,
uMIN_WHOLE_SD59x18,
UNIT,
uUNIT,
uUNIT_SQUARED,
ZERO
} from "./Constants.sol";
import { wrap } from "./Helpers.sol";
import { SD59x18 } from "./ValueType.sol";
/// @notice Calculates the absolute value of x.
///
/// @dev Requirements:
/// - x must be greater than `MIN_SD59x18`.
///
/// @param x The SD59x18 number for which to calculate the absolute value.
/// @param result The absolute value of x as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function abs(SD59x18 x) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
if (xInt == uMIN_SD59x18) {
revert Errors.PRBMath_SD59x18_Abs_MinSD59x18();
}
result = xInt < 0 ? wrap(-xInt) : x;
}
/// @notice Calculates the arithmetic average of x and y.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// @param x The first operand as an SD59x18 number.
/// @param y The second operand as an SD59x18 number.
/// @return result The arithmetic average as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function avg(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
int256 yInt = y.unwrap();
unchecked {
// This operation is equivalent to `x / 2 + y / 2`, and it can never overflow.
int256 sum = (xInt >> 1) + (yInt >> 1);
if (sum < 0) {
// If at least one of x and y is odd, add 1 to the result, because shifting negative numbers to the right
// rounds toward negative infinity. The right part is equivalent to `sum + (x % 2 == 1 || y % 2 == 1)`.
assembly ("memory-safe") {
result := add(sum, and(or(xInt, yInt), 1))
}
} else {
// Add 1 if both x and y are odd to account for the double 0.5 remainder truncated after shifting.
result = wrap(sum + (xInt & yInt & 1));
}
}
}
/// @notice Yields the smallest whole number greater than or equal to x.
///
/// @dev Optimized for fractional value inputs, because every whole value has (1e18 - 1) fractional counterparts.
/// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
///
/// Requirements:
/// - x must be less than or equal to `MAX_WHOLE_SD59x18`.
///
/// @param x The SD59x18 number to ceil.
/// @param result The smallest whole number greater than or equal to x, as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function ceil(SD59x18 x) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
if (xInt > uMAX_WHOLE_SD59x18) {
revert Errors.PRBMath_SD59x18_Ceil_Overflow(x);
}
int256 remainder = xInt % uUNIT;
if (remainder == 0) {
result = x;
} else {
unchecked {
// Solidity uses C fmod style, which returns a modulus with the same sign as x.
int256 resultInt = xInt - remainder;
if (xInt > 0) {
resultInt += uUNIT;
}
result = wrap(resultInt);
}
}
}
/// @notice Divides two SD59x18 numbers, returning a new SD59x18 number.
///
/// @dev This is an extension of {Common.mulDiv} for signed numbers, which works by computing the signs and the absolute
/// values separately.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv}.
/// - The result is rounded toward zero.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv}.
/// - None of the inputs can be `MIN_SD59x18`.
/// - The denominator must not be zero.
/// - The result must fit in SD59x18.
///
/// @param x The numerator as an SD59x18 number.
/// @param y The denominator as an SD59x18 number.
/// @param result The quotient as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function div(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
int256 yInt = y.unwrap();
if (xInt == uMIN_SD59x18 || yInt == uMIN_SD59x18) {
revert Errors.PRBMath_SD59x18_Div_InputTooSmall();
}
// Get hold of the absolute values of x and y.
uint256 xAbs;
uint256 yAbs;
unchecked {
xAbs = xInt < 0 ? uint256(-xInt) : uint256(xInt);
yAbs = yInt < 0 ? uint256(-yInt) : uint256(yInt);
}
// Compute the absolute value (x*UNIT÷y). The resulting value must fit in SD59x18.
uint256 resultAbs = Common.mulDiv(xAbs, uint256(uUNIT), yAbs);
if (resultAbs > uint256(uMAX_SD59x18)) {
revert Errors.PRBMath_SD59x18_Div_Overflow(x, y);
}
// Check if x and y have the same sign using two's complement representation. The left-most bit represents the sign (1 for
// negative, 0 for positive or zero).
bool sameSign = (xInt ^ yInt) > -1;
// If the inputs have the same sign, the result should be positive. Otherwise, it should be negative.
unchecked {
result = wrap(sameSign ? int256(resultAbs) : -int256(resultAbs));
}
}
/// @notice Calculates the natural exponent of x using the following formula:
///
/// $$
/// e^x = 2^{x * log_2{e}}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {exp2}.
///
/// Requirements:
/// - Refer to the requirements in {exp2}.
/// - x must be less than 133_084258667509499441.
///
/// @param x The exponent as an SD59x18 number.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp(SD59x18 x) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
// Any input less than the threshold returns zero.
// This check also prevents an overflow for very small numbers.
if (xInt < uEXP_MIN_THRESHOLD) {
return ZERO;
}
// This check prevents values greater than 192e18 from being passed to {exp2}.
if (xInt > uEXP_MAX_INPUT) {
revert Errors.PRBMath_SD59x18_Exp_InputTooBig(x);
}
unchecked {
// Inline the fixed-point multiplication to save gas.
int256 doubleUnitProduct = xInt * uLOG2_E;
result = exp2(wrap(doubleUnitProduct / uUNIT));
}
}
/// @notice Calculates the binary exponent of x using the binary fraction method using the following formula:
///
/// $$
/// 2^{-x} = \frac{1}{2^x}
/// $$
///
/// @dev See https://ethereum.stackexchange.com/q/79903/24693.
///
/// Notes:
/// - If x is less than -59_794705707972522261, the result is zero.
///
/// Requirements:
/// - x must be less than 192e18.
/// - The result must fit in SD59x18.
///
/// @param x The exponent as an SD59x18 number.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp2(SD59x18 x) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
if (xInt < 0) {
// The inverse of any number less than the threshold is truncated to zero.
if (xInt < uEXP2_MIN_THRESHOLD) {
return ZERO;
}
unchecked {
// Inline the fixed-point inversion to save gas.
result = wrap(uUNIT_SQUARED / exp2(wrap(-xInt)).unwrap());
}
} else {
// Numbers greater than or equal to 192e18 don't fit in the 192.64-bit format.
if (xInt > uEXP2_MAX_INPUT) {
revert Errors.PRBMath_SD59x18_Exp2_InputTooBig(x);
}
unchecked {
// Convert x to the 192.64-bit fixed-point format.
uint256 x_192x64 = uint256((xInt << 64) / uUNIT);
// It is safe to cast the result to int256 due to the checks above.
result = wrap(int256(Common.exp2(x_192x64)));
}
}
}
/// @notice Yields the greatest whole number less than or equal to x.
///
/// @dev Optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional
/// counterparts. See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
///
/// Requirements:
/// - x must be greater than or equal to `MIN_WHOLE_SD59x18`.
///
/// @param x The SD59x18 number to floor.
/// @param result The greatest whole number less than or equal to x, as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function floor(SD59x18 x) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
if (xInt < uMIN_WHOLE_SD59x18) {
revert Errors.PRBMath_SD59x18_Floor_Underflow(x);
}
int256 remainder = xInt % uUNIT;
if (remainder == 0) {
result = x;
} else {
unchecked {
// Solidity uses C fmod style, which returns a modulus with the same sign as x.
int256 resultInt = xInt - remainder;
if (xInt < 0) {
resultInt -= uUNIT;
}
result = wrap(resultInt);
}
}
}
/// @notice Yields the excess beyond the floor of x for positive numbers and the part of the number to the right.
/// of the radix point for negative numbers.
/// @dev Based on the odd function definition. https://en.wikipedia.org/wiki/Fractional_part
/// @param x The SD59x18 number to get the fractional part of.
/// @param result The fractional part of x as an SD59x18 number.
function frac(SD59x18 x) pure returns (SD59x18 result) {
result = wrap(x.unwrap() % uUNIT);
}
/// @notice Calculates the geometric mean of x and y, i.e. $\sqrt{x * y}$.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x * y must fit in SD59x18.
/// - x * y must not be negative, since complex numbers are not supported.
///
/// @param x The first operand as an SD59x18 number.
/// @param y The second operand as an SD59x18 number.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function gm(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
int256 yInt = y.unwrap();
if (xInt == 0 || yInt == 0) {
return ZERO;
}
unchecked {
// Equivalent to `xy / x != y`. Checking for overflow this way is faster than letting Solidity do it.
int256 xyInt = xInt * yInt;
if (xyInt / xInt != yInt) {
revert Errors.PRBMath_SD59x18_Gm_Overflow(x, y);
}
// The product must not be negative, since complex numbers are not supported.
if (xyInt < 0) {
revert Errors.PRBMath_SD59x18_Gm_NegativeProduct(x, y);
}
// We don't need to multiply the result by `UNIT` here because the x*y product picked up a factor of `UNIT`
// during multiplication. See the comments in {Common.sqrt}.
uint256 resultUint = Common.sqrt(uint256(xyInt));
result = wrap(int256(resultUint));
}
}
/// @notice Calculates the inverse of x.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x must not be zero.
///
/// @param x The SD59x18 number for which to calculate the inverse.
/// @return result The inverse as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function inv(SD59x18 x) pure returns (SD59x18 result) {
result = wrap(uUNIT_SQUARED / x.unwrap());
}
/// @notice Calculates the natural logarithm of x using the following formula:
///
/// $$
/// ln{x} = log_2{x} / log_2{e}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
/// - The precision isn't sufficiently fine-grained to return exactly `UNIT` when the input is `E`.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The SD59x18 number for which to calculate the natural logarithm.
/// @return result The natural logarithm as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function ln(SD59x18 x) pure returns (SD59x18 result) {
// Inline the fixed-point multiplication to save gas. This is overflow-safe because the maximum value that
// {log2} can return is ~195_205294292027477728.
result = wrap(log2(x).unwrap() * uUNIT / uLOG2_E);
}
/// @notice Calculates the common logarithm of x using the following formula:
///
/// $$
/// log_{10}{x} = log_2{x} / log_2{10}
/// $$
///
/// However, if x is an exact power of ten, a hard coded value is returned.
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The SD59x18 number for which to calculate the common logarithm.
/// @return result The common logarithm as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function log10(SD59x18 x) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
if (xInt < 0) {
revert Errors.PRBMath_SD59x18_Log_InputTooSmall(x);
}
// Note that the `mul` in this block is the standard multiplication operation, not {SD59x18.mul}.
// prettier-ignore
assembly ("memory-safe") {
switch x
case 1 { result := mul(uUNIT, sub(0, 18)) }
case 10 { result := mul(uUNIT, sub(1, 18)) }
case 100 { result := mul(uUNIT, sub(2, 18)) }
case 1000 { result := mul(uUNIT, sub(3, 18)) }
case 10000 { result := mul(uUNIT, sub(4, 18)) }
case 100000 { result := mul(uUNIT, sub(5, 18)) }
case 1000000 { result := mul(uUNIT, sub(6, 18)) }
case 10000000 { result := mul(uUNIT, sub(7, 18)) }
case 100000000 { result := mul(uUNIT, sub(8, 18)) }
case 1000000000 { result := mul(uUNIT, sub(9, 18)) }
case 10000000000 { result := mul(uUNIT, sub(10, 18)) }
case 100000000000 { result := mul(uUNIT, sub(11, 18)) }
case 1000000000000 { result := mul(uUNIT, sub(12, 18)) }
case 10000000000000 { result := mul(uUNIT, sub(13, 18)) }
case 100000000000000 { result := mul(uUNIT, sub(14, 18)) }
case 1000000000000000 { result := mul(uUNIT, sub(15, 18)) }
case 10000000000000000 { result := mul(uUNIT, sub(16, 18)) }
case 100000000000000000 { result := mul(uUNIT, sub(17, 18)) }
case 1000000000000000000 { result := 0 }
case 10000000000000000000 { result := uUNIT }
case 100000000000000000000 { result := mul(uUNIT, 2) }
case 1000000000000000000000 { result := mul(uUNIT, 3) }
case 10000000000000000000000 { result := mul(uUNIT, 4) }
case 100000000000000000000000 { result := mul(uUNIT, 5) }
case 1000000000000000000000000 { result := mul(uUNIT, 6) }
case 10000000000000000000000000 { result := mul(uUNIT, 7) }
case 100000000000000000000000000 { result := mul(uUNIT, 8) }
case 1000000000000000000000000000 { result := mul(uUNIT, 9) }
case 10000000000000000000000000000 { result := mul(uUNIT, 10) }
case 100000000000000000000000000000 { result := mul(uUNIT, 11) }
case 1000000000000000000000000000000 { result := mul(uUNIT, 12) }
case 10000000000000000000000000000000 { result := mul(uUNIT, 13) }
case 100000000000000000000000000000000 { result := mul(uUNIT, 14) }
case 1000000000000000000000000000000000 { result := mul(uUNIT, 15) }
case 10000000000000000000000000000000000 { result := mul(uUNIT, 16) }
case 100000000000000000000000000000000000 { result := mul(uUNIT, 17) }
case 1000000000000000000000000000000000000 { result := mul(uUNIT, 18) }
case 10000000000000000000000000000000000000 { result := mul(uUNIT, 19) }
case 100000000000000000000000000000000000000 { result := mul(uUNIT, 20) }
case 1000000000000000000000000000000000000000 { result := mul(uUNIT, 21) }
case 10000000000000000000000000000000000000000 { result := mul(uUNIT, 22) }
case 100000000000000000000000000000000000000000 { result := mul(uUNIT, 23) }
case 1000000000000000000000000000000000000000000 { result := mul(uUNIT, 24) }
case 10000000000000000000000000000000000000000000 { result := mul(uUNIT, 25) }
case 100000000000000000000000000000000000000000000 { result := mul(uUNIT, 26) }
case 1000000000000000000000000000000000000000000000 { result := mul(uUNIT, 27) }
case 10000000000000000000000000000000000000000000000 { result := mul(uUNIT, 28) }
case 100000000000000000000000000000000000000000000000 { result := mul(uUNIT, 29) }
case 1000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 30) }
case 10000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 31) }
case 100000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 32) }
case 1000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 33) }
case 10000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 34) }
case 100000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 35) }
case 1000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 36) }
case 10000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 37) }
case 100000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 38) }
case 1000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 39) }
case 10000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 40) }
case 100000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 41) }
case 1000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 42) }
case 10000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 43) }
case 100000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 44) }
case 1000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 45) }
case 10000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 46) }
case 100000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 47) }
case 1000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 48) }
case 10000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 49) }
case 100000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 50) }
case 1000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 51) }
case 10000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 52) }
case 100000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 53) }
case 1000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 54) }
case 10000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 55) }
case 100000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 56) }
case 1000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 57) }
case 10000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 58) }
default { result := uMAX_SD59x18 }
}
if (result.unwrap() == uMAX_SD59x18) {
unchecked {
// Inline the fixed-point division to save gas.
result = wrap(log2(x).unwrap() * uUNIT / uLOG2_10);
}
}
}
/// @notice Calculates the binary logarithm of x using the iterative approximation algorithm:
///
/// $$
/// log_2{x} = n + log_2{y}, \text{ where } y = x*2^{-n}, \ y \in [1, 2)
/// $$
///
/// For $0 \leq x \lt 1$, the input is inverted:
///
/// $$
/// log_2{x} = -log_2{\frac{1}{x}}
/// $$
///
/// @dev See https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation.
///
/// Notes:
/// - Due to the lossy precision of the iterative approximation, the results are not perfectly accurate to the last decimal.
///
/// Requirements:
/// - x must be greater than zero.
///
/// @param x The SD59x18 number for which to calculate the binary logarithm.
/// @return result The binary logarithm as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function log2(SD59x18 x) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
if (xInt <= 0) {
revert Errors.PRBMath_SD59x18_Log_InputTooSmall(x);
}
unchecked {
int256 sign;
if (xInt >= uUNIT) {
sign = 1;
} else {
sign = -1;
// Inline the fixed-point inversion to save gas.
xInt = uUNIT_SQUARED / xInt;
}
// Calculate the integer part of the logarithm.
uint256 n = Common.msb(uint256(xInt / uUNIT));
// This is the integer part of the logarithm as an SD59x18 number. The operation can't overflow
// because n is at most 255, `UNIT` is 1e18, and the sign is either 1 or -1.
int256 resultInt = int256(n) * uUNIT;
// Calculate $y = x * 2^{-n}$.
int256 y = xInt >> n;
// If y is the unit number, the fractional part is zero.
if (y == uUNIT) {
return wrap(resultInt * sign);
}
// Calculate the fractional part via the iterative approximation.
// The `delta >>= 1` part is equivalent to `delta /= 2`, but shifting bits is more gas efficient.
int256 DOUBLE_UNIT = 2e18;
for (int256 delta = uHALF_UNIT; delta > 0; delta >>= 1) {
y = (y * y) / uUNIT;
// Is y^2 >= 2e18 and so in the range [2e18, 4e18)?
if (y >= DOUBLE_UNIT) {
// Add the 2^{-m} factor to the logarithm.
resultInt = resultInt + delta;
// Halve y, which corresponds to z/2 in the Wikipedia article.
y >>= 1;
}
}
resultInt *= sign;
result = wrap(resultInt);
}
}
/// @notice Multiplies two SD59x18 numbers together, returning a new SD59x18 number.
///
/// @dev Notes:
/// - Refer to the notes in {Common.mulDiv18}.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv18}.
/// - None of the inputs can be `MIN_SD59x18`.
/// - The result must fit in SD59x18.
///
/// @param x The multiplicand as an SD59x18 number.
/// @param y The multiplier as an SD59x18 number.
/// @return result The product as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function mul(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
int256 yInt = y.unwrap();
if (xInt == uMIN_SD59x18 || yInt == uMIN_SD59x18) {
revert Errors.PRBMath_SD59x18_Mul_InputTooSmall();
}
// Get hold of the absolute values of x and y.
uint256 xAbs;
uint256 yAbs;
unchecked {
xAbs = xInt < 0 ? uint256(-xInt) : uint256(xInt);
yAbs = yInt < 0 ? uint256(-yInt) : uint256(yInt);
}
// Compute the absolute value (x*y÷UNIT). The resulting value must fit in SD59x18.
uint256 resultAbs = Common.mulDiv18(xAbs, yAbs);
if (resultAbs > uint256(uMAX_SD59x18)) {
revert Errors.PRBMath_SD59x18_Mul_Overflow(x, y);
}
// Check if x and y have the same sign using two's complement representation. The left-most bit represents the sign (1 for
// negative, 0 for positive or zero).
bool sameSign = (xInt ^ yInt) > -1;
// If the inputs have the same sign, the result should be positive. Otherwise, it should be negative.
unchecked {
result = wrap(sameSign ? int256(resultAbs) : -int256(resultAbs));
}
}
/// @notice Raises x to the power of y using the following formula:
///
/// $$
/// x^y = 2^{log_2{x} * y}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {exp2}, {log2}, and {mul}.
/// - Returns `UNIT` for 0^0.
///
/// Requirements:
/// - Refer to the requirements in {exp2}, {log2}, and {mul}.
///
/// @param x The base as an SD59x18 number.
/// @param y Exponent to raise x to, as an SD59x18 number
/// @return result x raised to power y, as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function pow(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
int256 yInt = y.unwrap();
// If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero.
if (xInt == 0) {
return yInt == 0 ? UNIT : ZERO;
}
// If x is `UNIT`, the result is always `UNIT`.
else if (xInt == uUNIT) {
return UNIT;
}
// If y is zero, the result is always `UNIT`.
if (yInt == 0) {
return UNIT;
}
// If y is `UNIT`, the result is always x.
else if (yInt == uUNIT) {
return x;
}
// Calculate the result using the formula.
result = exp2(mul(log2(x), y));
}
/// @notice Raises x (an SD59x18 number) to the power y (an unsigned basic integer) using the well-known
/// algorithm "exponentiation by squaring".
///
/// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv18}.
/// - Returns `UNIT` for 0^0.
///
/// Requirements:
/// - Refer to the requirements in {abs} and {Common.mulDiv18}.
/// - The result must fit in SD59x18.
///
/// @param x The base as an SD59x18 number.
/// @param y The exponent as a uint256.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function powu(SD59x18 x, uint256 y) pure returns (SD59x18 result) {
uint256 xAbs = uint256(abs(x).unwrap());
// Calculate the first iteration of the loop in advance.
uint256 resultAbs = y & 1 > 0 ? xAbs : uint256(uUNIT);
// Equivalent to `for(y /= 2; y > 0; y /= 2)`.
uint256 yAux = y;
for (yAux >>= 1; yAux > 0; yAux >>= 1) {
xAbs = Common.mulDiv18(xAbs, xAbs);
// Equivalent to `y % 2 == 1`.
if (yAux & 1 > 0) {
resultAbs = Common.mulDiv18(resultAbs, xAbs);
}
}
// The result must fit in SD59x18.
if (resultAbs > uint256(uMAX_SD59x18)) {
revert Errors.PRBMath_SD59x18_Powu_Overflow(x, y);
}
unchecked {
// Is the base negative and the exponent odd? If yes, the result should be negative.
int256 resultInt = int256(resultAbs);
bool isNegative = x.unwrap() < 0 && y & 1 == 1;
if (isNegative) {
resultInt = -resultInt;
}
result = wrap(resultInt);
}
}
/// @notice Calculates the square root of x using the Babylonian method.
///
/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
///
/// Notes:
/// - Only the positive root is returned.
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x cannot be negative, since complex numbers are not supported.
/// - x must be less than `MAX_SD59x18 / UNIT`.
///
/// @param x The SD59x18 number for which to calculate the square root.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function sqrt(SD59x18 x) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
if (xInt < 0) {
revert Errors.PRBMath_SD59x18_Sqrt_NegativeInput(x);
}
if (xInt > uMAX_SD59x18 / uUNIT) {
revert Errors.PRBMath_SD59x18_Sqrt_Overflow(x);
}
unchecked {
// Multiply x by `UNIT` to account for the factor of `UNIT` picked up when multiplying two SD59x18 numbers.
// In this case, the two numbers are both the square root.
uint256 resultUint = Common.sqrt(uint256(xInt * uUNIT));
result = wrap(int256(resultUint));
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { wrap } from "./Casting.sol";
import { SD59x18 } from "./ValueType.sol";
/// @notice Implements the checked addition operation (+) in the SD59x18 type.
function add(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
return wrap(x.unwrap() + y.unwrap());
}
/// @notice Implements the AND (&) bitwise operation in the SD59x18 type.
function and(SD59x18 x, int256 bits) pure returns (SD59x18 result) {
return wrap(x.unwrap() & bits);
}
/// @notice Implements the AND (&) bitwise operation in the SD59x18 type.
function and2(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
return wrap(x.unwrap() & y.unwrap());
}
/// @notice Implements the equal (=) operation in the SD59x18 type.
function eq(SD59x18 x, SD59x18 y) pure returns (bool result) {
result = x.unwrap() == y.unwrap();
}
/// @notice Implements the greater than operation (>) in the SD59x18 type.
function gt(SD59x18 x, SD59x18 y) pure returns (bool result) {
result = x.unwrap() > y.unwrap();
}
/// @notice Implements the greater than or equal to operation (>=) in the SD59x18 type.
function gte(SD59x18 x, SD59x18 y) pure returns (bool result) {
result = x.unwrap() >= y.unwrap();
}
/// @notice Implements a zero comparison check function in the SD59x18 type.
function isZero(SD59x18 x) pure returns (bool result) {
result = x.unwrap() == 0;
}
/// @notice Implements the left shift operation (<<) in the SD59x18 type.
function lshift(SD59x18 x, uint256 bits) pure returns (SD59x18 result) {
result = wrap(x.unwrap() << bits);
}
/// @notice Implements the lower than operation (<) in the SD59x18 type.
function lt(SD59x18 x, SD59x18 y) pure returns (bool result) {
result = x.unwrap() < y.unwrap();
}
/// @notice Implements the lower than or equal to operation (<=) in the SD59x18 type.
function lte(SD59x18 x, SD59x18 y) pure returns (bool result) {
result = x.unwrap() <= y.unwrap();
}
/// @notice Implements the unchecked modulo operation (%) in the SD59x18 type.
function mod(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
result = wrap(x.unwrap() % y.unwrap());
}
/// @notice Implements the not equal operation (!=) in the SD59x18 type.
function neq(SD59x18 x, SD59x18 y) pure returns (bool result) {
result = x.unwrap() != y.unwrap();
}
/// @notice Implements the NOT (~) bitwise operation in the SD59x18 type.
function not(SD59x18 x) pure returns (SD59x18 result) {
result = wrap(~x.unwrap());
}
/// @notice Implements the OR (|) bitwise operation in the SD59x18 type.
function or(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
result = wrap(x.unwrap() | y.unwrap());
}
/// @notice Implements the right shift operation (>>) in the SD59x18 type.
function rshift(SD59x18 x, uint256 bits) pure returns (SD59x18 result) {
result = wrap(x.unwrap() >> bits);
}
/// @notice Implements the checked subtraction operation (-) in the SD59x18 type.
function sub(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
result = wrap(x.unwrap() - y.unwrap());
}
/// @notice Implements the checked unary minus operation (-) in the SD59x18 type.
function unary(SD59x18 x) pure returns (SD59x18 result) {
result = wrap(-x.unwrap());
}
/// @notice Implements the unchecked addition operation (+) in the SD59x18 type.
function uncheckedAdd(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
unchecked {
result = wrap(x.unwrap() + y.unwrap());
}
}
/// @notice Implements the unchecked subtraction operation (-) in the SD59x18 type.
function uncheckedSub(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
unchecked {
result = wrap(x.unwrap() - y.unwrap());
}
}
/// @notice Implements the unchecked unary minus operation (-) in the SD59x18 type.
function uncheckedUnary(SD59x18 x) pure returns (SD59x18 result) {
unchecked {
result = wrap(-x.unwrap());
}
}
/// @notice Implements the XOR (^) bitwise operation in the SD59x18 type.
function xor(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
result = wrap(x.unwrap() ^ y.unwrap());
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import "./Errors.sol" as CastingErrors;
import { MAX_UINT128, MAX_UINT40 } from "../Common.sol";
import { uMAX_SD1x18, uMIN_SD1x18 } from "../sd1x18/Constants.sol";
import { SD1x18 } from "../sd1x18/ValueType.sol";
import { uMAX_UD2x18 } from "../ud2x18/Constants.sol";
import { UD2x18 } from "../ud2x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { SD59x18 } from "./ValueType.sol";
/// @notice Casts an SD59x18 number into int256.
/// @dev This is basically a functional alias for {unwrap}.
function intoInt256(SD59x18 x) pure returns (int256 result) {
result = SD59x18.unwrap(x);
}
/// @notice Casts an SD59x18 number into SD1x18.
/// @dev Requirements:
/// - x must be greater than or equal to `uMIN_SD1x18`.
/// - x must be less than or equal to `uMAX_SD1x18`.
function intoSD1x18(SD59x18 x) pure returns (SD1x18 result) {
int256 xInt = SD59x18.unwrap(x);
if (xInt < uMIN_SD1x18) {
revert CastingErrors.PRBMath_SD59x18_IntoSD1x18_Underflow(x);
}
if (xInt > uMAX_SD1x18) {
revert CastingErrors.PRBMath_SD59x18_IntoSD1x18_Overflow(x);
}
result = SD1x18.wrap(int64(xInt));
}
/// @notice Casts an SD59x18 number into UD2x18.
/// @dev Requirements:
/// - x must be positive.
/// - x must be less than or equal to `uMAX_UD2x18`.
function intoUD2x18(SD59x18 x) pure returns (UD2x18 result) {
int256 xInt = SD59x18.unwrap(x);
if (xInt < 0) {
revert CastingErrors.PRBMath_SD59x18_IntoUD2x18_Underflow(x);
}
if (xInt > int256(uint256(uMAX_UD2x18))) {
revert CastingErrors.PRBMath_SD59x18_IntoUD2x18_Overflow(x);
}
result = UD2x18.wrap(uint64(uint256(xInt)));
}
/// @notice Casts an SD59x18 number into UD60x18.
/// @dev Requirements:
/// - x must be positive.
function intoUD60x18(SD59x18 x) pure returns (UD60x18 result) {
int256 xInt = SD59x18.unwrap(x);
if (xInt < 0) {
revert CastingErrors.PRBMath_SD59x18_IntoUD60x18_Underflow(x);
}
result = UD60x18.wrap(uint256(xInt));
}
/// @notice Casts an SD59x18 number into uint256.
/// @dev Requirements:
/// - x must be positive.
function intoUint256(SD59x18 x) pure returns (uint256 result) {
int256 xInt = SD59x18.unwrap(x);
if (xInt < 0) {
revert CastingErrors.PRBMath_SD59x18_IntoUint256_Underflow(x);
}
result = uint256(xInt);
}
/// @notice Casts an SD59x18 number into uint128.
/// @dev Requirements:
/// - x must be positive.
/// - x must be less than or equal to `uMAX_UINT128`.
function intoUint128(SD59x18 x) pure returns (uint128 result) {
int256 xInt = SD59x18.unwrap(x);
if (xInt < 0) {
revert CastingErrors.PRBMath_SD59x18_IntoUint128_Underflow(x);
}
if (xInt > int256(uint256(MAX_UINT128))) {
revert CastingErrors.PRBMath_SD59x18_IntoUint128_Overflow(x);
}
result = uint128(uint256(xInt));
}
/// @notice Casts an SD59x18 number into uint40.
/// @dev Requirements:
/// - x must be positive.
/// - x must be less than or equal to `MAX_UINT40`.
function intoUint40(SD59x18 x) pure returns (uint40 result) {
int256 xInt = SD59x18.unwrap(x);
if (xInt < 0) {
revert CastingErrors.PRBMath_SD59x18_IntoUint40_Underflow(x);
}
if (xInt > int256(uint256(MAX_UINT40))) {
revert CastingErrors.PRBMath_SD59x18_IntoUint40_Overflow(x);
}
result = uint40(uint256(xInt));
}
/// @notice Alias for {wrap}.
function sd(int256 x) pure returns (SD59x18 result) {
result = SD59x18.wrap(x);
}
/// @notice Alias for {wrap}.
function sd59x18(int256 x) pure returns (SD59x18 result) {
result = SD59x18.wrap(x);
}
/// @notice Unwraps an SD59x18 number into int256.
function unwrap(SD59x18 x) pure returns (int256 result) {
result = SD59x18.unwrap(x);
}
/// @notice Wraps an int256 number into SD59x18.
function wrap(int256 x) pure returns (SD59x18 result) {
result = SD59x18.wrap(x);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import "../Common.sol" as Common;
import "./Errors.sol" as CastingErrors;
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { UD2x18 } from "../ud2x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { SD1x18 } from "./ValueType.sol";
/// @notice Casts an SD1x18 number into SD59x18.
/// @dev There is no overflow check because the domain of SD1x18 is a subset of SD59x18.
function intoSD59x18(SD1x18 x) pure returns (SD59x18 result) {
result = SD59x18.wrap(int256(SD1x18.unwrap(x)));
}
/// @notice Casts an SD1x18 number into UD2x18.
/// - x must be positive.
function intoUD2x18(SD1x18 x) pure returns (UD2x18 result) {
int64 xInt = SD1x18.unwrap(x);
if (xInt < 0) {
revert CastingErrors.PRBMath_SD1x18_ToUD2x18_Underflow(x);
}
result = UD2x18.wrap(uint64(xInt));
}
/// @notice Casts an SD1x18 number into UD60x18.
/// @dev Requirements:
/// - x must be positive.
function intoUD60x18(SD1x18 x) pure returns (UD60x18 result) {
int64 xInt = SD1x18.unwrap(x);
if (xInt < 0) {
revert CastingErrors.PRBMath_SD1x18_ToUD60x18_Underflow(x);
}
result = UD60x18.wrap(uint64(xInt));
}
/// @notice Casts an SD1x18 number into uint256.
/// @dev Requirements:
/// - x must be positive.
function intoUint256(SD1x18 x) pure returns (uint256 result) {
int64 xInt = SD1x18.unwrap(x);
if (xInt < 0) {
revert CastingErrors.PRBMath_SD1x18_ToUint256_Underflow(x);
}
result = uint256(uint64(xInt));
}
/// @notice Casts an SD1x18 number into uint128.
/// @dev Requirements:
/// - x must be positive.
function intoUint128(SD1x18 x) pure returns (uint128 result) {
int64 xInt = SD1x18.unwrap(x);
if (xInt < 0) {
revert CastingErrors.PRBMath_SD1x18_ToUint128_Underflow(x);
}
result = uint128(uint64(xInt));
}
/// @notice Casts an SD1x18 number into uint40.
/// @dev Requirements:
/// - x must be positive.
/// - x must be less than or equal to `MAX_UINT40`.
function intoUint40(SD1x18 x) pure returns (uint40 result) {
int64 xInt = SD1x18.unwrap(x);
if (xInt < 0) {
revert CastingErrors.PRBMath_SD1x18_ToUint40_Underflow(x);
}
if (xInt > int64(uint64(Common.MAX_UINT40))) {
revert CastingErrors.PRBMath_SD1x18_ToUint40_Overflow(x);
}
result = uint40(uint64(xInt));
}
/// @notice Alias for {wrap}.
function sd1x18(int64 x) pure returns (SD1x18 result) {
result = SD1x18.wrap(x);
}
/// @notice Unwraps an SD1x18 number into int64.
function unwrap(SD1x18 x) pure returns (int64 result) {
result = SD1x18.unwrap(x);
}
/// @notice Wraps an int64 number into SD1x18.
function wrap(int64 x) pure returns (SD1x18 result) {
result = SD1x18.wrap(x);
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { UD2x18 } from "./ValueType.sol";
/// @notice Thrown when trying to cast a UD2x18 number that doesn't fit in SD1x18.
error PRBMath_UD2x18_IntoSD1x18_Overflow(UD2x18 x);
/// @notice Thrown when trying to cast a UD2x18 number that doesn't fit in uint40.
error PRBMath_UD2x18_IntoUint40_Overflow(UD2x18 x);// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { SD59x18 } from "./ValueType.sol";
/// @notice Thrown when taking the absolute value of `MIN_SD59x18`.
error PRBMath_SD59x18_Abs_MinSD59x18();
/// @notice Thrown when ceiling a number overflows SD59x18.
error PRBMath_SD59x18_Ceil_Overflow(SD59x18 x);
/// @notice Thrown when converting a basic integer to the fixed-point format overflows SD59x18.
error PRBMath_SD59x18_Convert_Overflow(int256 x);
/// @notice Thrown when converting a basic integer to the fixed-point format underflows SD59x18.
error PRBMath_SD59x18_Convert_Underflow(int256 x);
/// @notice Thrown when dividing two numbers and one of them is `MIN_SD59x18`.
error PRBMath_SD59x18_Div_InputTooSmall();
/// @notice Thrown when dividing two numbers and one of the intermediary unsigned results overflows SD59x18.
error PRBMath_SD59x18_Div_Overflow(SD59x18 x, SD59x18 y);
/// @notice Thrown when taking the natural exponent of a base greater than 133_084258667509499441.
error PRBMath_SD59x18_Exp_InputTooBig(SD59x18 x);
/// @notice Thrown when taking the binary exponent of a base greater than 192e18.
error PRBMath_SD59x18_Exp2_InputTooBig(SD59x18 x);
/// @notice Thrown when flooring a number underflows SD59x18.
error PRBMath_SD59x18_Floor_Underflow(SD59x18 x);
/// @notice Thrown when taking the geometric mean of two numbers and their product is negative.
error PRBMath_SD59x18_Gm_NegativeProduct(SD59x18 x, SD59x18 y);
/// @notice Thrown when taking the geometric mean of two numbers and multiplying them overflows SD59x18.
error PRBMath_SD59x18_Gm_Overflow(SD59x18 x, SD59x18 y);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD1x18.
error PRBMath_SD59x18_IntoSD1x18_Overflow(SD59x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD1x18.
error PRBMath_SD59x18_IntoSD1x18_Underflow(SD59x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD2x18.
error PRBMath_SD59x18_IntoUD2x18_Overflow(SD59x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD2x18.
error PRBMath_SD59x18_IntoUD2x18_Underflow(SD59x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD60x18.
error PRBMath_SD59x18_IntoUD60x18_Underflow(SD59x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint128.
error PRBMath_SD59x18_IntoUint128_Overflow(SD59x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint128.
error PRBMath_SD59x18_IntoUint128_Underflow(SD59x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint256.
error PRBMath_SD59x18_IntoUint256_Underflow(SD59x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint40.
error PRBMath_SD59x18_IntoUint40_Overflow(SD59x18 x);
/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint40.
error PRBMath_SD59x18_IntoUint40_Underflow(SD59x18 x);
/// @notice Thrown when taking the logarithm of a number less than or equal to zero.
error PRBMath_SD59x18_Log_InputTooSmall(SD59x18 x);
/// @notice Thrown when multiplying two numbers and one of the inputs is `MIN_SD59x18`.
error PRBMath_SD59x18_Mul_InputTooSmall();
/// @notice Thrown when multiplying two numbers and the intermediary absolute result overflows SD59x18.
error PRBMath_SD59x18_Mul_Overflow(SD59x18 x, SD59x18 y);
/// @notice Thrown when raising a number to a power and the intermediary absolute result overflows SD59x18.
error PRBMath_SD59x18_Powu_Overflow(SD59x18 x, uint256 y);
/// @notice Thrown when taking the square root of a negative number.
error PRBMath_SD59x18_Sqrt_NegativeInput(SD59x18 x);
/// @notice Thrown when the calculating the square root overflows SD59x18.
error PRBMath_SD59x18_Sqrt_Overflow(SD59x18 x);// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { SD1x18 } from "./ValueType.sol";
/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in UD2x18.
error PRBMath_SD1x18_ToUD2x18_Underflow(SD1x18 x);
/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in UD60x18.
error PRBMath_SD1x18_ToUD60x18_Underflow(SD1x18 x);
/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint128.
error PRBMath_SD1x18_ToUint128_Underflow(SD1x18 x);
/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint256.
error PRBMath_SD1x18_ToUint256_Underflow(SD1x18 x);
/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint40.
error PRBMath_SD1x18_ToUint40_Overflow(SD1x18 x);
/// @notice Thrown when trying to cast a SD1x18 number that doesn't fit in uint40.
error PRBMath_SD1x18_ToUint40_Underflow(SD1x18 x);{
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"remappings": [
"forge-std/=lib/forge-std/src/",
"@prb/math/=lib/prb-math/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/"
]
}Contract ABI
API[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"CAMPAIGN_PAUSED","type":"error"},{"inputs":[],"name":"CHECK_QAS_OR_KPIS","type":"error"},{"inputs":[{"internalType":"string","name":"kpi","type":"string"}],"name":"DUPLICATE_KPI","type":"error"},{"inputs":[{"internalType":"string","name":"qa","type":"string"}],"name":"DUPLICATE_QA","type":"error"},{"inputs":[{"internalType":"string","name":"kpiKind","type":"string"}],"name":"GIVEN_KPI_KIND_DOESNT_EXIST_ON_CAMPAIGN","type":"error"},{"inputs":[{"internalType":"string","name":"qaKind","type":"string"}],"name":"GIVEN_QA_KIND_DOESNT_EXIST_ON_CAMPAIGN","type":"error"},{"inputs":[],"name":"INVALID_CONTENT_LINK","type":"error"},{"inputs":[],"name":"NOT_ELIGIBLE_FOR_WITHDRAW","type":"error"},{"inputs":[],"name":"NO_CONTENT_AUTHOR","type":"error"},{"inputs":[],"name":"ONLY_CLAIMABLE_STATUS","type":"error"},{"inputs":[],"name":"ONLY_CONTENTS","type":"error"},{"inputs":[],"name":"ONLY_FACTORY","type":"error"},{"inputs":[],"name":"ONLY_FINALIZER_ORACLE","type":"error"},{"inputs":[],"name":"ONLY_FINISHED_CAMPAIGN_STATUS","type":"error"},{"inputs":[{"internalType":"enum Status","name":"status","type":"uint8"}],"name":"ONLY_IN_PROGRESS_STATUS","type":"error"},{"inputs":[],"name":"ONLY_OWNER","type":"error"},{"inputs":[],"name":"ONLY_PROTOCOL_ADMIN","type":"error"},{"inputs":[{"internalType":"enum Status","name":"status","type":"uint8"}],"name":"ONLY_UPCOMING_OR_IN_PROGRESS_STATUS","type":"error"},{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"name":"PRBMath_MulDiv18_Overflow","type":"error"},{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"},{"internalType":"uint256","name":"denominator","type":"uint256"}],"name":"PRBMath_MulDiv_Overflow","type":"error"},{"inputs":[{"internalType":"UD60x18","name":"x","type":"uint256"}],"name":"PRBMath_UD60x18_Exp2_InputTooBig","type":"error"},{"inputs":[{"internalType":"UD60x18","name":"x","type":"uint256"}],"name":"PRBMath_UD60x18_Log_InputTooSmall","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"addedAmount","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"lastBudget","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"newBudget","type":"uint256"},{"indexed":false,"internalType":"address","name":"donator","type":"address"},{"indexed":false,"internalType":"address","name":"campaign","type":"address"}],"name":"BudgetIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"finalizer","type":"address"},{"indexed":true,"internalType":"enum Finalizer","name":"finalizerType","type":"uint8"},{"indexed":true,"internalType":"uint256","name":"refundedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newBudget","type":"uint256"},{"indexed":false,"internalType":"address","name":"campaign","type":"address"}],"name":"CampaignFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"finishedAt","type":"uint256"},{"indexed":false,"internalType":"address","name":"campaign","type":"address"}],"name":"CampaignFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"paused","type":"bool"},{"indexed":false,"internalType":"address","name":"campaign","type":"address"}],"name":"CampaignPauseToggled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"contentCreator","type":"address"},{"indexed":true,"internalType":"uint256","name":"rewardedAmount","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"refundAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalPaidAmount","type":"uint256"},{"indexed":false,"internalType":"string","name":"contentLink","type":"string"},{"indexed":false,"internalType":"address","name":"campaign","type":"address"}],"name":"Claimed","type":"event"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"applicationFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"contentLink","type":"string"}],"name":"applyContent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"budgetInfo","outputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint128","name":"maxPerPost","type":"uint128"},{"internalType":"uint128","name":"reservedAmount","type":"uint128"}],"internalType":"struct BudgetInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"contentLink","type":"string"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"contentEffectiveKPI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contents","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentStatus","outputs":[{"internalType":"enum Status","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"details","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"ipfsCID","type":"string"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"}],"internalType":"struct BasicInfo","name":"basicInfo_","type":"tuple"},{"components":[{"internalType":"uint64","name":"initTime","type":"uint64"},{"internalType":"address","name":"clonedCampaign","type":"address"},{"internalType":"address","name":"implementation","type":"address"}],"internalType":"struct CampaignInitData","name":"initData_","type":"tuple"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint128","name":"maxPerPost","type":"uint128"},{"internalType":"uint128","name":"reservedAmount","type":"uint128"}],"internalType":"struct BudgetInfo","name":"budgetInfo_","type":"tuple"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint256","name":"decimals","type":"uint256"}],"internalType":"struct Token","name":"token_","type":"tuple"},{"components":[{"internalType":"string","name":"social","type":"string"},{"components":[{"internalType":"uint8","name":"pct","type":"uint8"},{"internalType":"uint248","name":"min","type":"uint248"},{"internalType":"uint256","name":"ratio","type":"uint256"},{"internalType":"string","name":"method","type":"string"},{"internalType":"string","name":"extra","type":"string"}],"internalType":"struct KPI[]","name":"kpis","type":"tuple[]"}],"internalType":"struct SocialKPIs","name":"socialKPIs_","type":"tuple"},{"components":[{"internalType":"enum QA_METHOD","name":"method","type":"uint8"},{"internalType":"uint88","name":"pct","type":"uint88"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"string","name":"kind","type":"string"}],"internalType":"struct QualificationData[]","name":"qaData_","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"totalPosts","type":"uint256"},{"internalType":"uint256","name":"totalEligibleContents","type":"uint256"},{"internalType":"uint256","name":"totalEffectiveKPIs","type":"uint256"},{"internalType":"uint256","name":"lastPostTime","type":"uint256"},{"internalType":"uint256","name":"lastUpdatedTime","type":"uint256"},{"internalType":"uint256","name":"applicationFee","type":"uint256"}],"internalType":"struct PostInfo","name":"postInfo_","type":"tuple"},{"internalType":"enum Status","name":"status_","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"finalizeFinishedCampaign","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"finalizeInProgressCampaign","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"contentLink","type":"string"}],"name":"getContentCreator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"increaseBudget","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsCID","type":"string"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint128","name":"maxPerPost","type":"uint128"},{"internalType":"uint128","name":"reservedAmount","type":"uint128"}],"internalType":"struct BudgetInfo","name":"budgetInfo_","type":"tuple"},{"components":[{"internalType":"string","name":"social","type":"string"},{"components":[{"internalType":"uint8","name":"pct","type":"uint8"},{"internalType":"uint248","name":"min","type":"uint248"},{"internalType":"uint256","name":"ratio","type":"uint256"},{"internalType":"string","name":"method","type":"string"},{"internalType":"string","name":"extra","type":"string"}],"internalType":"struct KPI[]","name":"kpis","type":"tuple[]"}],"internalType":"struct SocialKPIs","name":"socialKPIs_","type":"tuple"},{"components":[{"internalType":"enum QA_METHOD","name":"method","type":"uint8"},{"internalType":"uint88","name":"pct","type":"uint88"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"string","name":"kind","type":"string"}],"internalType":"struct QualificationData[]","name":"qaData_","type":"tuple[]"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nameHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"qaData","outputs":[{"components":[{"internalType":"enum QA_METHOD","name":"method","type":"uint8"},{"internalType":"uint88","name":"pct","type":"uint88"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"string","name":"kind","type":"string"}],"internalType":"struct QualificationData[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"refundAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"registeredActionKind","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"enum QA_METHOD","name":"method","type":"uint8"},{"internalType":"uint88","name":"pct","type":"uint88"},{"internalType":"address","name":"oracle","type":"address"},{"internalType":"string","name":"kind","type":"string"}],"internalType":"struct QualificationData[]","name":"qas","type":"tuple[]"},{"components":[{"internalType":"uint8","name":"pct","type":"uint8"},{"internalType":"uint248","name":"min","type":"uint248"},{"internalType":"uint256","name":"ratio","type":"uint256"},{"internalType":"string","name":"method","type":"string"},{"internalType":"string","name":"extra","type":"string"}],"internalType":"struct KPI[]","name":"kpis","type":"tuple[]"}],"name":"rewardEstimation","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"socialKPIs","outputs":[{"components":[{"internalType":"string","name":"social","type":"string"},{"components":[{"internalType":"uint8","name":"pct","type":"uint8"},{"internalType":"uint248","name":"min","type":"uint248"},{"internalType":"uint256","name":"ratio","type":"uint256"},{"internalType":"string","name":"method","type":"string"},{"internalType":"string","name":"extra","type":"string"}],"internalType":"struct KPI[]","name":"kpis","type":"tuple[]"}],"internalType":"struct SocialKPIs","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"startTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"togglePauseCampaign","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"contentLink","type":"string"},{"internalType":"uint256","name":"additionalContentEffectiveKPI","type":"uint256"},{"internalType":"bool","name":"additional","type":"bool"}],"name":"updateKPIs","outputs":[{"internalType":"uint256","name":"totalEffectiveKPIs","type":"uint256"},{"internalType":"uint256","name":"contentEffectiveKPI_","type":"uint256"},{"internalType":"uint256","name":"totalEligibleContents","type":"uint256"},{"internalType":"int8","name":"eligibilityPolarity","type":"int8"}],"stateMutability":"nonpayable","type":"function"}]Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$107.56
Net Worth in FRAX
105.436935
Token Allocations
FRXUSD
100.00%
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|---|---|---|---|---|
| FRAXTAL | 100.00% | $0.999156 | 107.6482 | $107.56 |
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.