-
Notifications
You must be signed in to change notification settings - Fork 376
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
507 additions
and
0 deletions.
There are no files selected for viewing
173 changes: 173 additions & 0 deletions
173
packages/protocol/contracts-0.8/stability/FeeCurrencyWrapper.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.7 <0.8.20; | ||
|
||
import "@openzeppelin/contracts8/access/Ownable.sol"; | ||
import "@openzeppelin/contracts8/token/ERC20/IERC20.sol"; | ||
import "forge-std/console.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 "./IFeeCurrency.sol"; | ||
import "./IDecimals.sol"; | ||
|
||
contract FeeCurrencyWrapper is Initializable, CalledByVm { | ||
IFeeCurrency public wrappedToken; | ||
|
||
uint96 public digitDifference; | ||
|
||
uint256 public debited; | ||
|
||
string public name; | ||
string public symbol; | ||
|
||
/** | ||
* @notice Sets initialized == true on implementation contracts | ||
* @param test Set to true to skip implementation initialization | ||
*/ | ||
constructor(bool test) public Initializable(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); | ||
} | ||
|
||
/** | ||
* @notice Used in place of the constructor to allow the contract to be upgradable via proxy. | ||
* @param _wrappedToken The address of the wrapped token. | ||
* @param _name The name of the wrapped token. | ||
* @param _symbol The symbol of the wrapped token. | ||
* @param _expectedDecimals The expected number of decimals of the wrapped token. | ||
*/ | ||
function initialize(address _wrappedToken, string memory _name, string memory _symbol, uint8 _expectedDecimals) | ||
external | ||
initializer | ||
{ | ||
wrappedToken = IFeeCurrency(_wrappedToken); | ||
name = _name; | ||
symbol = _symbol; | ||
uint8 decimals = IDecimals(_wrappedToken).decimals(); | ||
digitDifference = uint96(10**(_expectedDecimals - decimals)); | ||
} | ||
|
||
/** | ||
* @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) public view returns (uint256) { | ||
return upscale(wrappedToken.balanceOf(account)); | ||
} | ||
|
||
/** | ||
* Downscales value to the wrapped token's native digits and debits it. | ||
* @param from from address | ||
* @param value Debited value in the wrapped digits. | ||
*/ | ||
function debitGasFees(address from, uint256 value) external onlyVm { | ||
uint256 toDebit = downscale(value); | ||
require(toDebit > 0, "Must debit at least one token."); | ||
debited = toDebit; | ||
wrappedToken.debitGasFees(from, toDebit); | ||
} | ||
|
||
/** | ||
* Downscales value to the wrapped token's native digits and credits it. | ||
* @param recipients The recipients | ||
* @param amounts The amounts (in wrapped token digits) | ||
*/ | ||
function creditGasFees(address[] calldata recipients, uint256[] calldata amounts) public onlyVm { | ||
if (debited == 0) { | ||
return; | ||
} | ||
require(recipients.length == amounts.length, "Recipients and amounts must be the same length."); | ||
|
||
uint256[] memory scaledAmounts = new uint256[](amounts.length); | ||
|
||
uint256 totalToBeCredited = 0; | ||
|
||
for (uint256 i = 0; i < amounts.length; i++) { | ||
scaledAmounts[i] = downscale(amounts[i]); | ||
totalToBeCredited += scaledAmounts[i]; | ||
} | ||
|
||
require(totalToBeCredited <= debited, "Cannot credit more than debited."); | ||
|
||
uint256 roundingError = debited - totalToBeCredited; | ||
if (roundingError > 0) { | ||
scaledAmounts[0] += roundingError; | ||
} | ||
|
||
wrappedToken.creditGasFees(recipients, scaledAmounts); | ||
debited = 0; | ||
} | ||
|
||
/** | ||
* Downscales value to the wrapped 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 wrapped token digits). | ||
* @param tipAmount The amount to tip (in wrapped token digits). | ||
* @param _gatewayFeeAmount The amount of the gateway fee (in wrapped token digits). Unused. | ||
* @param baseFeeAmount The amount of the base fee (in wrapped token digits). | ||
*/ | ||
function creditGasFees( | ||
address refundRecipient, | ||
address tipRecipient, | ||
address _gatewayFeeRecipient, | ||
address baseFeeRecipient, | ||
uint256 refundAmount, | ||
uint256 tipAmount, | ||
uint256 _gatewayFeeAmount, | ||
uint256 baseFeeAmount | ||
) public onlyVm { | ||
if (debited == 0) { | ||
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; | ||
} | ||
wrappedToken.creditGasFees( | ||
refundRecipient, | ||
tipRecipient, | ||
address(0), | ||
baseFeeRecipient, | ||
refundScaled, | ||
tipTxFeeScaled, | ||
0, | ||
baseTxFeeScaled | ||
); | ||
|
||
debited = 0; | ||
} | ||
|
||
function upscale(uint256 value) internal view returns (uint256) { | ||
return value * digitDifference; | ||
} | ||
|
||
function downscale(uint256 value) internal view returns (uint256) { | ||
return value / digitDifference; | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
packages/protocol/contracts-0.8/stability/interfaces/IDecimals.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
pragma solidity ^0.8.13; | ||
|
||
import "@openzeppelin/contracts8/token/ERC20/IERC20.sol"; | ||
|
||
interface IDecimals is IERC20 { | ||
function decimals() external view returns (uint8); | ||
} |
72 changes: 72 additions & 0 deletions
72
packages/protocol/contracts-0.8/stability/interfaces/IFeeCurrency.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
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. | ||
Notes on compatibility: | ||
- There are two versions of `creditGasFees`: one for the current | ||
(2024-01-16) blockchain implementation and a more future-proof version | ||
that avoids deprecated fields and allows new recipients that might | ||
become necessary on later blockchain implementations. Both versions | ||
should be implemented to increase compatibility. | ||
- Future Celo blockchain implementations might provide a way for plain | ||
ERC-20 tokens to be used as gas currencies without implementing this | ||
interface. If this sounds preferable to you, please contact cLabs | ||
before implementing this interface for your token. | ||
*/ | ||
|
||
// 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; | ||
|
||
// New function signature, will be used when all fee currencies have migrated. | ||
// Credited amounts are gas refund, base fee and tip. Additional components | ||
// might be added, like an L1 gas fee when Celo becomes and L2. | ||
// - The implementation must increase each `recipient`'s balance by respective `value`. | ||
// - Must revert if `msg.sender` is not the zero address. | ||
// - Must revert if `recipients` and `amounts` have different lengths. | ||
function creditGasFees(address[] calldata recipients, uint256[] calldata amounts) external; | ||
|
||
// Old function signature for backwards compatibility | ||
// - Must revert if `msg.sender` is not the zero address. | ||
// - `refund` must be credited to `from` | ||
// - `tipTxFee` must be credited to `feeRecipient` | ||
// - `baseTxFee` must be credited to `communityFund` | ||
// - `gatewayFeeRecipient` and `gatewayFee` only exist for backwards | ||
// compatibility reasons and will always be zero. | ||
function creditGasFees( | ||
address from, | ||
address feeRecipient, | ||
address gatewayFeeRecipient, | ||
address communityFund, | ||
uint256 refund, | ||
uint256 tipTxFee, | ||
uint256 gatewayFee, | ||
uint256 baseTxFee | ||
) external; | ||
} |
Oops, something went wrong.