Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CR11 merge to master #10965

Merged
merged 22 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a1d10fb
Release: Sorted oracles update (#10891)
pahor167 Jan 27, 2024
e0e3578
FeeCurrency Adapter (#10907)
pahor167 Jan 29, 2024
b167121
Calculation of unlockable gold (#10731)
pahor167 Jan 30, 2024
2e27a3d
Gas Price Minimum should never be zero (#10909)
martinvol Jan 30, 2024
4bf5e92
GasPriceMinimum backward compatibility fix (#10922)
pahor167 Feb 6, 2024
c7a5553
Storage gap for FeeCurrencyAdapter (#10933)
pahor167 Feb 9, 2024
0bfa0e3
Debit 0 value check (#10930)
pahor167 Feb 9, 2024
5bc7105
Removal of SortedOracle multiplier (#10931)
pahor167 Feb 12, 2024
1d71364
Make super of FeeCurrencyAdapterOwnable.sol explicit (#10944)
martinvol Feb 20, 2024
874eeac
Added note to FeeCurrencyAdapter.sol initializer (#10943)
martinvol Feb 20, 2024
6604de7
FeeAdapter debit round up (#10940)
pahor167 Feb 20, 2024
f407d72
Code quality post audit (#10945)
pahor167 Feb 20, 2024
d6b6e62
SortedOracles sourced from Mento core for CR10 (#10946)
pahor167 Feb 22, 2024
7a2d29e
Update gap size to follow 50 rule (#10948)
pahor167 Feb 22, 2024
901cd42
CR11 fix verification (#10951)
pahor167 Feb 28, 2024
a698731
Merge branch 'master' into pahor/cr11_merge
pahor167 Apr 4, 2024
f1bbeb2
yarn lock
pahor167 Apr 4, 2024
dd0899b
Updates specification of SrotedOracles (#10970)
martinvol Apr 12, 2024
0f0833a
Update Natspec for FeeAdapter (#10969)
martinvol Apr 18, 2024
e62a16e
Merge branch 'release/core-contracts/11' into pahor/cr11_merge
pahor167 Apr 18, 2024
59465b7
merge
pahor167 Apr 18, 2024
2ee4c78
dockerfile
pahor167 Apr 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions packages/protocol/contracts-0.8/common/GasPriceMinimum.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "../../contracts/common/interfaces/ICeloVersionedContract.sol";
import "../../contracts/common/FixidityLib.sol";
import "./UsingRegistry.sol";
import "../../contracts/stability/interfaces/ISortedOracles.sol";
import "@openzeppelin/contracts8/utils/math/Math.sol";

/**
* @title Stores and provides gas price minimum for various currencies.
Expand Down Expand Up @@ -38,6 +39,7 @@ contract GasPriceMinimum is
FixidityLib.Fraction public adjustmentSpeed;

uint256 public baseFeeOpCodeActivationBlock;
uint256 public constant ABSOLUTE_MINIMAL_GAS_PRICE = 1;

/**
* @notice Sets initialized == true on implementation contracts
Expand All @@ -53,7 +55,7 @@ contract GasPriceMinimum is
* @return Patch version of the contract.
*/
function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) {
return (1, 2, 0, 0);
return (1, 2, 0, 1);
}

/**
Expand Down Expand Up @@ -150,12 +152,7 @@ contract GasPriceMinimum is
}
}

/**
* @notice Retrieve the current gas price minimum for a currency.
* @param tokenAddress The currency the gas price should be in (defaults to gold).
* @return current gas price minimum in the requested currency
*/
function getGasPriceMinimum(address tokenAddress) external view returns (uint256) {
function _getGasPriceMinimum(address tokenAddress) private view returns (uint256) {
if (
tokenAddress == address(0) ||
tokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)
Expand All @@ -172,6 +169,21 @@ contract GasPriceMinimum is
}
}

/**
* @notice Retrieve the current gas price minimum for a currency.
* When caled for 0x0 or Celo address, it returns gasPriceMinimum().
* For other addresses it returns gasPriceMinimum() mutiplied by
* the SortedOracles median of the token. It does not check tokenAddress is a valid fee currency.
* this function will never returns values less than ABSOLUTE_MINIMAL_GAS_PRICE.
* If Oracle rate doesn't exist, it returns ABSOLUTE_MINIMAL_GAS_PRICE.
* @dev This functions assumes one unit of token has 18 digits.
* @param tokenAddress The currency the gas price should be in (defaults to Celo).
* @return current gas price minimum in the requested currency
*/
function getGasPriceMinimum(address tokenAddress) external view returns (uint256) {
return Math.max(_getGasPriceMinimum(tokenAddress), ABSOLUTE_MINIMAL_GAS_PRICE);
}

/**
* @notice Adjust the gas price minimum based on governable parameters
* and block congestion.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.7 <0.8.20;

import "./FeeCurrencyAdapterOwnable.sol";

contract CeloFeeCurrencyAdapterOwnable is FeeCurrencyAdapterOwnable {
/**
* @notice Sets initialized == true on implementation contracts
* @param test Set to true to skip implementation initialization
*/
constructor(bool test) FeeCurrencyAdapterOwnable(test) {}

/**
* @notice Returns the storage, major, minor, and patch version of the contract.
* @return Storage version of the contract.
* @return Major version of the contract.
* @return Minor version of the contract.
* @return Patch version of the contract.
*/
function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) {
return (1, 1, 0, 0);
}
}
182 changes: 182 additions & 0 deletions packages/protocol/contracts-0.8/stability/FeeCurrencyAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.7 <0.8.20;

import "@openzeppelin/contracts8/access/Ownable.sol";
import "@openzeppelin/contracts8/token/ERC20/IERC20.sol";

import "../../contracts/common/CalledByVm.sol";
import "../../contracts/common/Initializable.sol";
import "../../contracts/common/interfaces/ICeloVersionedContract.sol";
import "../../contracts/common/FixidityLib.sol";
import "../../contracts/stability/interfaces/ISortedOracles.sol";
import "./interfaces/IFeeCurrency.sol";
import "./interfaces/IDecimals.sol";
import "./interfaces/IFeeCurrencyAdapter.sol";

contract FeeCurrencyAdapter is Initializable, CalledByVm, IFeeCurrencyAdapter {
IFeeCurrency public adaptedToken;

uint96 public digitDifference;

uint256 public debited = 0;

string public name;
string public symbol;

uint8 public expectedDecimals;

uint256[44] __gap;

/**
* @notice Sets initialized == true on implementation contracts
* @param test Set to true to skip implementation initialization
*/
constructor(bool test) public Initializable(test) {}

/**
* @notice Used in place of the constructor to allow the contract to be upgradable via proxy.
* @param _adaptedToken The address of the adapted token.
* @param _name The name of the adapted token.
* @param _symbol The symbol of the adapted token.
* @param _expectedDecimals The expected number of decimals of the adapted token.
* @notice _expectedDecimals must be bigger than _adaptedToken.decimals().
*/
function initialize(
address _adaptedToken,
string memory _name,
string memory _symbol,
uint8 _expectedDecimals
) public virtual initializer {
_setAdaptedToken(_adaptedToken);
name = _name;
symbol = _symbol;
uint8 _decimals = IDecimals(_adaptedToken).decimals();
require(
_decimals < _expectedDecimals,
"Decimals of adapted token must be < expected decimals."
);
digitDifference = uint96(10**(_expectedDecimals - _decimals));
expectedDecimals = _expectedDecimals;
}

/**
* Downscales value to the adapted token's native digits and debits it.
* @param from from address
* @param value Debited value in the adapted digits.
*/
function debitGasFees(address from, uint256 value) external onlyVm {
uint256 valueScaled = downscale(value);
require(valueScaled > 0, "Scaled debit value must be > 0.");
debited = valueScaled;
adaptedToken.debitGasFees(from, valueScaled);
}

/**
* Downscales value to the adapted token's native digits and credits it.
* @param refundRecipient The recipient of the refund.
* @param tipRecipient The recipient of the tip.
* @param _gatewayFeeRecipient The recipient of the gateway fee. Unused.
* @param baseFeeRecipient The recipient of the base fee.
* @param refundAmount The amount to refund (in adapted token digits).
* @param tipAmount The amount to tip (in adapted token digits).
* @param _gatewayFeeAmount The amount of the gateway fee (in adapted token digits). Unused.
* @param baseFeeAmount The amount of the base fee (in adapted token digits).
*/
function creditGasFees(
address refundRecipient,
address tipRecipient,
address _gatewayFeeRecipient,
address baseFeeRecipient,
uint256 refundAmount,
uint256 tipAmount,
uint256 _gatewayFeeAmount,
uint256 baseFeeAmount
) external onlyVm {
if (debited == 0) {
// When eth.estimateGas is called, this function is called but we don't want to credit anything.
return;
}

uint256 refundScaled = downscale(refundAmount);
uint256 tipTxFeeScaled = downscale(tipAmount);
uint256 baseTxFeeScaled = downscale(baseFeeAmount);

require(
refundScaled + tipTxFeeScaled + baseTxFeeScaled <= debited,
"Cannot credit more than debited."
);

uint256 roundingError = debited - (refundScaled + tipTxFeeScaled + baseTxFeeScaled);

if (roundingError > 0) {
baseTxFeeScaled += roundingError;
}
adaptedToken.creditGasFees(
refundRecipient,
tipRecipient,
address(0),
baseFeeRecipient,
refundScaled,
tipTxFeeScaled,
0,
baseTxFeeScaled
);

debited = 0;
}

/**
* @notice Returns adapted token address.
* @return The adapted token address.
*/
function getAdaptedToken() external view returns (address) {
return address(adaptedToken);
}

/**
* @notice Gets the balance of the specified address with correct digits.
* @param account The address to query the balance of.
* @return The balance of the specified address.
*/
function balanceOf(address account) external view returns (uint256) {
return upscale(adaptedToken.balanceOf(account));
}

/**
* @notice Gets the total supply with correct digits.
* @return The total supply.
*/
function totalSupply() external view returns (uint256) {
return upscale(adaptedToken.totalSupply());
}

/**
* @notice Gets the total supply with correct digits.
* @return The total supply.
*/
function decimals() external view returns (uint8) {
return expectedDecimals;
}

function upscale(uint256 value) internal view returns (uint256) {
return value * digitDifference;
}

/**
* @notice Downscales value to the adapted token's native digits.
* @dev Downscale is rounding up in favour of protocol. User possibly can pay a bit more than expected (up to 1 unit of a token).
* Example:
* USDC has 6 decimals and in such case user can pay up to 0.000001 USDC more than expected.
* WBTC (currently not supported by Celo chain as fee currency) has 8 decimals and in such case user can pay up to 0.00000001 WBTC more than expected.
* Considering the current price of WBTC, it's less than 0.0005 USD. Even when WBTC price would be 1 mil USD, it's still would be only 0.01 USD.
* In general it is a very small amount and it is acceptable to round up in favor of the protocol.
* @param value The value to downscale.
*/
function downscale(uint256 value) internal view returns (uint256) {
return (value + digitDifference - 1) / digitDifference;
}

function _setAdaptedToken(address _adaptedToken) internal virtual {
adaptedToken = IFeeCurrency(_adaptedToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.7 <0.8.20;

import "@openzeppelin/contracts8/access/Ownable.sol";
import "@openzeppelin/contracts8/token/ERC20/IERC20.sol";

import "./FeeCurrencyAdapter.sol";

contract FeeCurrencyAdapterOwnable is FeeCurrencyAdapter, Ownable {
uint256[49] __gap2;

/**
* @notice Sets initialized == true on implementation contracts
* @param test Set to true to skip implementation initialization
*/
constructor(bool test) FeeCurrencyAdapter(test) {}

/**
* @notice Used in place of the constructor to allow the contract to be upgradable via proxy.
* @param _adaptedToken The address of the adapted token.
* @param _name The name of the adapted token.
* @param _symbol The symbol of the adapted token.
* @param _expectedDecimals The expected number of decimals of the adapted token.
*/
function initialize(
address _adaptedToken,
string memory _name,
string memory _symbol,
uint8 _expectedDecimals
) public override {
_transferOwnership(msg.sender);
FeeCurrencyAdapter.initialize(_adaptedToken, _name, _symbol, _expectedDecimals);
}

/**
* @notice Sets adapted token address.
* @param _adaptedToken The address of the adapted token.
*/
function setAdaptedToken(address _adaptedToken) public onlyOwner {
_setAdaptedToken(_adaptedToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma solidity ^0.8.13;

interface IDecimals {
function decimals() external view returns (uint8);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
pragma solidity ^0.8.13;

import "@openzeppelin/contracts8/token/ERC20/IERC20.sol";

interface IFeeCurrency is IERC20 {
/*
This interface should be implemented for tokens which are supposed to
act as fee currencies on the Celo blockchain, meaning that they can be
used to pay gas fees for CIP-64 transactions (and some older tx types).
See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0064.md

Before executing a tx with non-empty feeCurrency field, the fee
currency's `debitGasFees` function is called to reserve the maximum
amount that tx can spend on gas. After the tx has been executed, the
`creditGasFees` function is called to refund the unused gas and credit
the spent fees to the correct recipients. Events which are raised inside
these functions will show up for every transaction using the token as a
fee currency.

Requirements:
- The functions will be called by the blockchain client with `msg.sender
== address(0)`. If this condition is not met, the functions must
revert to prevent malicious users from crediting their accounts directly.
- `creditGasFees` must credit all specified amounts. If it impossible to
credit one of the recipients for some reason, add the amount to the
value credited to the first valid recipient. This is important to keep
the debited and credited amounts consistent.
*/

// Called before transaction execution to reserve the maximum amount of gas
// that can be used by the transaction.
// - The implementation must reduce `from`'s balance by `value`.
// - Must revert if `msg.sender` is not the zero address.
function debitGasFees(address from, uint256 value) external;

/**
* Called after transaction execution to refund the unused gas and credit the
* spent fees to the correct recipients.
* @param refundRecipient The recipient of the refund.
* @param tipRecipient The recipient of the tip.
* @param _gatewayFeeRecipient The recipient of the gateway fee. Unused.
* @param baseFeeRecipient The recipient of the base fee.
* @param refundAmount The amount to refund.
* @param tipAmount The amount to tip.
* @param _gatewayFeeAmount The amount of the gateway fee. Unused.
* @param baseFeeAmount The amount of the base fee.
*/
function creditGasFees(
address refundRecipient,
address tipRecipient,
address _gatewayFeeRecipient,
address baseFeeRecipient,
uint256 refundAmount,
uint256 tipAmount,
uint256 _gatewayFeeAmount,
uint256 baseFeeAmount
) external;
}
Loading
Loading