Skip to content

Commit

Permalink
Merge pull request #46 from P4-Games/feature/natspec-comments
Browse files Browse the repository at this point in the history
Feature/natspec comments
  • Loading branch information
tomasfrancizco authored Dec 27, 2024
2 parents ed89e6c + c7f7d7f commit 5d95b61
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 84 deletions.
212 changes: 150 additions & 62 deletions src/L2/ChatterPay.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

pragma solidity ^0.8.24;

/*//////////////////////////////////////////////////////////////
IMPORTS
//////////////////////////////////////////////////////////////*/

import {IAccount, UserOperation} from "lib/entry-point-v6/interfaces/IAccount.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
Expand All @@ -14,10 +10,6 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IEntryPoint} from "lib/entry-point-v6/interfaces/IEntryPoint.sol";
import {ITokensPriceFeeds} from "../Ethereum/TokensPriceFeeds.sol";

/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/

error ChatterPay__NotFromEntryPoint();
error ChatterPay__NotFromEntryPointOrOwner();
error ChatterPay__ExecuteCallFailed(bytes);
Expand All @@ -29,26 +21,17 @@ error ChatterPay__InvalidTokenReceiver();
error ChatterPay__NoTokenBalance(address);
error ChatterPay__BalanceTxFailed();

/*//////////////////////////////////////////////////////////////
INTERFACES
//////////////////////////////////////////////////////////////*/

interface IERC20 {
function symbol() external view returns (string memory);

function decimals() external view returns (uint8);

function balanceOf(address account) external view returns (uint256);

function transfer(address to, uint256 value) external returns (bool);
}

/*//////////////////////////////////////////////////////////////
CONTRACT
//////////////////////////////////////////////////////////////*/

contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/

IEntryPoint private s_entryPoint;
address public s_paymaster;
address public s_api3PriceFeed;
Expand All @@ -57,10 +40,6 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {

uint256 public constant FEE_IN_CENTS = 50; // 50 cents

/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/

event Execution(
address indexed wallet,
address indexed dest,
Expand All @@ -76,10 +55,6 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
event EntryPointSet(address indexed entryPoint);
event WithdrawBalance(address[] indexed, address indexed to);

/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/

modifier requireFromEntryPoint() {
if (msg.sender != address(s_entryPoint)) {
revert ChatterPay__NotFromEntryPoint();
Expand All @@ -94,10 +69,13 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
_;
}

/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/

/**
* @notice Initializes the ChatterPay contract.
* @dev Sets up the owner, entry point, and paymaster, and initializes supported tokens.
* @param _entryPoint The address of the entry point contract.
* @param _newOwner The address of the contract owner.
* @param _paymaster The address of the paymaster contract.
*/
function initialize(
address _entryPoint,
address _newOwner,
Expand All @@ -111,13 +89,17 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
s_supportedNotStableTokens = ["WETH", "WBTC"];
}

/*//////////////////////////////////////////////////////////////
EXTERNAL AND PUBLIC FUNCTIONS
//////////////////////////////////////////////////////////////*/

receive() external payable {}

// Generic execute function
/**
* @notice Executes a generic transaction.
* @dev Allows the contract to call an external contract with the provided data.
* @param dest The destination address of the transaction.
* @param value The amount of ETH to send with the transaction.
* @param functionData The calldata for the function to be executed.
* @custom:events Emits an `Execution` event on success.
* @custom:reverts ChatterPay__ExecuteCallFailed if the transaction fails.
*/
function execute(
address dest,
uint256 value,
Expand All @@ -132,12 +114,21 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
emit Execution(address(this), dest, value, functionData);
}

/**
* @notice Executes a token transfer with an additional fee.
* @dev Transfers the token and pays the specified fee to the paymaster.
* @param dest The destination address of the token transfer.
* @param fee The fee in tokens to be paid to the paymaster.
* @param functionData The calldata for the token transfer function.
* @custom:events Emits a `TokenTransfer` event on success.
* @custom:reverts ChatterPay__ExecuteCallFailed if the fee or transfer fails.
*/
function executeTokenTransfer(
address dest,
uint256 fee,
bytes calldata functionData
) external requireFromEntryPointOrOwner {
if (fee != calculateFee(dest, FEE_IN_CENTS))
if (fee != _calculateFee(dest, FEE_IN_CENTS))
revert ChatterPay__ExecuteCallFailed("Incorrect fee");

(bool feeTxSuccess, bytes memory feeTxResult) = dest.call(
Expand All @@ -160,7 +151,14 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
emit TokenTransfer(address(this), dest, fee, functionData);
}

// A signature is valid, if it's the ChatterPay owner
/**
* @notice Validates a user operation.
* @dev Validates the signature and ensures sufficient funds for the operation.
* @param userOp The user operation being validated.
* @param userOpHash The hash of the user operation.
* @param missingAccountFunds The amount of funds required to cover the operation.
* @return validationData A value indicating the validation status of the user operation.
*/
function validateUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
Expand All @@ -171,6 +169,18 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
_payPrefund(missingAccountFunds);
}

/**
* @notice Withdraws token and ETH balances from the contract to a specified address.
* @dev Allows the contract owner to withdraw all balances of specified tokens and ETH.
* @param tokenAddresses The addresses of the tokens to withdraw.
* @param to The address to send the balances to.
* @return A boolean indicating whether the withdrawal was successful.
* @custom:events Emits a `WithdrawBalance` event on success.
* @custom:reverts ChatterPay__InvalidAmountOfTokens if the number of tokens exceeds the supported count.
* @custom:reverts ChatterPay__InvalidTokenReceiver if the recipient address is invalid.
* @custom:reverts ChatterPay__NoTokenBalance if a token has no balance.
* @custom:reverts ChatterPay__BalanceTxFailed if a token transfer or ETH transfer fails.
*/
function withdrawBalance(
address[] memory tokenAddresses,
address to
Expand Down Expand Up @@ -204,19 +214,33 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
return true;
}

/**
* @notice Sets the entry point address.
* @dev Only callable by the contract owner.
* @param _entryPoint The new entry point address.
* @custom:events Emits an `EntryPointSet` event on success.
*/
function setEntryPoint(address _entryPoint) external onlyOwner {
s_entryPoint = IEntryPoint(_entryPoint);
emit EntryPointSet(_entryPoint);
}

/**
* @notice Sets the API3 price feed address.
* @dev Only callable by the contract owner.
* @param _priceFeed The address of the API3 price feed contract.
*/
function setPriceFeedAddress(address _priceFeed) public onlyOwner {
s_api3PriceFeed = _priceFeed;
}

/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/

/**
* @notice Validates the signature of a user operation.
* @dev Recovers the signer from the operation hash and checks if it matches the contract owner.
* @param userOp The user operation containing the signature.
* @param userOpHash The hash of the user operation.
* @return validationData A value indicating whether the signature is valid (0 = valid, 1 = invalid).
*/
function _validateSignature(
UserOperation calldata userOp,
bytes32 userOpHash
Expand All @@ -229,6 +253,11 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
return 0;
}

/**
* @notice Pays the missing funds required for a user operation.
* @dev Sends the required funds to the entry point to cover operation costs.
* @param missingAccountFunds The amount of funds required to cover the operation.
*/
function _payPrefund(uint256 missingAccountFunds) internal {
if (missingAccountFunds != 0) {
(bool success, ) = payable(msg.sender).call{
Expand All @@ -239,25 +268,43 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
}
}

function calculateFee(
/**
* @notice Calculates the fee for a transaction in token units.
* @dev Uses the token's symbol, decimals, and price (if not stable) to compute the fee.
* @param _token The address of the token for which the fee is calculated.
* @param _cents The fee amount in cents (USD).
* @return The calculated fee in token units.
* @custom:reverts ChatterPay__UnsopportedTokenDecimals if the token decimals are unsupported.
* @custom:reverts ChatterPay__UnsopportedToken if the token is not supported.
*/
function _calculateFee(
address _token,
uint256 _cents
) internal view returns (uint256) {
string memory symbol = getTokenSymbol(_token);
bool isStable = isStableToken(symbol);
uint256 decimals = getTokenDecimals(_token);
string memory symbol = _getTokenSymbol(_token);
bool isStable = _isStableToken(symbol);
uint256 decimals = _getTokenDecimals(_token);
uint256 oraclePrice;
uint256 fee;
if (!isStable) {
oraclePrice = getAPI3OraclePrice(symbol);
fee = calculateFeeNotStable(oraclePrice, _cents);
oraclePrice = _getAPI3OraclePrice(symbol);
fee = _calculateFeeNotStable(oraclePrice, _cents);
} else {
fee = calculateFeeStable(decimals, _cents);
fee = _calculateFeeStable(decimals, _cents);
}
return fee;
}

function isStableToken(string memory _symbol) internal view returns (bool) {
/**
* @notice Checks whether a token is a stable token.
* @dev Compares the token symbol with the supported stable tokens list.
* @param _symbol The symbol of the token to check.
* @return A boolean indicating whether the token is stable (true = stable).
* @custom:reverts ChatterPay__UnsopportedToken if the token symbol is not recognized.
*/
function _isStableToken(
string memory _symbol
) internal view returns (bool) {
string[1] memory m_supportedStableTokens = s_supportedStableTokens;
string[2]
memory m_supportedNotStableTokens = s_supportedNotStableTokens;
Expand All @@ -280,7 +327,15 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
revert ChatterPay__UnsopportedToken();
}

function calculateFeeStable(
/**
* @notice Calculates the fee for a stable token based on its decimals.
* @dev Multiplies the fee in cents by a factor determined by the token's decimals.
* @param _decimals The number of decimals for the stable token.
* @param _cents The fee amount in cents (USD).
* @return The calculated fee in token units.
* @custom:reverts ChatterPay__UnsopportedTokenDecimals if the token decimals are unsupported.
*/
function _calculateFeeStable(
uint256 _decimals,
uint256 _cents
) internal pure returns (uint256) {
Expand All @@ -295,7 +350,14 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
return fee;
}

function calculateFeeNotStable(
/**
* @notice Calculates the fee for a non-stable token using an oracle price.
* @dev Uses the oracle price to determine the equivalent token fee for the specified cents.
* @param oraclePrice The price of the token from the oracle in USD (18 decimals).
* @param cents The fee amount in cents (USD).
* @return The calculated fee in token units.
*/
function _calculateFeeNotStable(
uint256 oraclePrice,
uint256 cents
) internal pure returns (uint256) {
Expand All @@ -304,17 +366,31 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
return fee;
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

/*//////////////////////////////////////////////////////////////
GETTERS
//////////////////////////////////////////////////////////////*/

/**
* @notice Authorizes an upgrade to a new implementation.
* @dev Only callable by the contract owner.
* @param newImplementation The address of the new implementation contract.
*/
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}

/**
* @notice Gets the current entry point address.
* @return The address of the entry point.
*/
function getEntryPoint() external view returns (address) {
return address(s_entryPoint);
}

function getAPI3OraclePrice(
/**
* @notice Gets the price of a token from the API3 oracle.
* @dev Queries the API3 oracle for the price of a token.
* @param _token The symbol of the token to query.
* @return The price of the token in USD.
* @custom:reverts ChatterPay__API3Failed if the API3 oracle call fails.
*/
function _getAPI3OraclePrice(
string memory _token
) internal view returns (uint256) {
if (s_api3PriceFeed == address(0)) revert ChatterPay__API3Failed();
Expand All @@ -339,13 +415,25 @@ contract ChatterPay is IAccount, UUPSUpgradeable, OwnableUpgradeable {
return price;
}

function getTokenDecimals(address token) internal view returns (uint8) {
/**
* @notice Gets the number of decimals for a token.
* @param token The address of the token.
* @return The number of decimals for the token.
*/
function _getTokenDecimals(address token) internal view returns (uint8) {
return IERC20(token).decimals();
}

function getTokenSymbol(
/**
* @notice Gets the symbol of a token.
* @param token The address of the token.
* @return The symbol of the token.
*/
function _getTokenSymbol(
address token
) internal view returns (string memory) {
return IERC20(token).symbol();
}

uint256[50] private __gap;
}
Loading

0 comments on commit 5d95b61

Please sign in to comment.