diff --git a/src/interfaces/IAssetPool.sol b/src/interfaces/IAssetPool.sol index 10a20c0..1f20f9a 100644 --- a/src/interfaces/IAssetPool.sol +++ b/src/interfaces/IAssetPool.sol @@ -6,20 +6,24 @@ pragma solidity ^0.8.20; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IXToken} from "./IXToken.sol"; import {ILPRegistry} from "./ILPRegistry.sol"; +import {IAssetOracle} from "./IAssetOracle.sol"; interface IAssetPool { enum CycleState { ACTIVE, // Normal operation - REBALANCING // LPs rebalancing reserves + REBALANCING // LPs rebalancing reserves } + // -------------------------------------------------------------------------------- + // EVENTS + // -------------------------------------------------------------------------------- event DepositRequested(address indexed user, uint256 amount, uint256 indexed cycleIndex); event DepositCancelled(address indexed user, uint256 amount, uint256 indexed cycleIndex); event AssetClaimed(address indexed user, uint256 amount, uint256 indexed cycleIndex); event BurnRequested(address indexed user, uint256 xTokenAmount, uint256 indexed cycleIndex); event BurnCancelled(address indexed user, uint256 amount, uint256 indexed cycleIndex); event ReserveWithdrawn(address indexed user, uint256 amount, uint256 indexed cycleIndex); - event Rebalanced(address indexed lp, uint256 amount, bool isDeficit, uint256 indexed cycleIndex); + event Rebalanced(address indexed lp, uint256 amount, bool isDeposit, uint256 indexed cycleIndex); event CycleStarted(uint256 indexed cycleIndex, uint256 timestamp); event CycleTimeUpdated(uint256 newCycleTime); event RebalanceTimeUpdated(uint256 newRebalanceTime); @@ -30,6 +34,9 @@ interface IAssetPool { int256 rebalanceAmount ); + // -------------------------------------------------------------------------------- + // ERRORS + // -------------------------------------------------------------------------------- error InvalidAmount(); error InsufficientBalance(); error NotLP(); @@ -39,28 +46,36 @@ interface IAssetPool { error ZeroAddress(); error NothingToClaim(); error NothingToCancel(); + error MintOrBurnPending(); - // User actions + // -------------------------------------------------------------------------------- + // USER ACTIONS + // -------------------------------------------------------------------------------- function depositReserve(uint256 amount) external; function cancelDeposit() external; function mintAsset(address user) external; - function burnAsset(uint256 xTokenAmount) external; + function burnAsset(uint256 assetAmount) external; function cancelBurn() external; function withdrawReserve(address user) external; - // LP actions + // -------------------------------------------------------------------------------- + // LP ACTIONS + // -------------------------------------------------------------------------------- function initiateRebalance() external; function rebalancePool(address lp, uint256 amount, bool isDeposit) external; - // Governance actions + // -------------------------------------------------------------------------------- + // GOVERNANCE ACTIONS + // -------------------------------------------------------------------------------- function updateCycleTime(uint256 newCycleTime) external; function updateRebalanceTime(uint256 newRebalanceTime) external; function pausePool() external; function unpausePool() external; - // View functions + // -------------------------------------------------------------------------------- + // VIEW FUNCTIONS + // -------------------------------------------------------------------------------- function getGeneralInfo() external view returns ( - uint256 _reserveBalance, uint256 _xTokenSupply, CycleState _cycleState, uint256 _cycleIndex, @@ -76,25 +91,34 @@ interface IAssetPool { int256 _rebalanceAmount ); - // State getters + // -------------------------------------------------------------------------------- + // STATE GETTERS + // -------------------------------------------------------------------------------- function reserveToken() external view returns (IERC20); function assetToken() external view returns (IXToken); function lpRegistry() external view returns (ILPRegistry); + function assetOracle() external view returns (IAssetOracle); + function cycleIndex() external view returns (uint256); function cycleState() external view returns (CycleState); function nextRebalanceStartDate() external view returns (uint256); function nextRebalanceEndDate() external view returns (uint256); function cycleTime() external view returns (uint256); function rebalanceTime() external view returns (uint256); - function reserveBalance() external view returns (uint256); - function totalDepositRequests() external view returns (uint256); - function totalRedemptionRequests() external view returns (uint256); - function totalRedemptionScaledRequests() external view returns (uint256); + + function totalReserveBalance() external view returns (uint256); + function newReserveSupply() external view returns (uint256); + function newAssetSupply() external view returns (uint256); + function netReserveDelta() external view returns (int256); function rebalanceAmount() external view returns (int256); + function rebalancedLPs() external view returns (uint256); function hasRebalanced(address lp) external view returns (bool); - function depositRequests(address user) external view returns (uint256); - function redemptionScaledRequests(address user) external view returns (uint256); + + function cycleTotalDepositRequests(uint256 cycle) external view returns (uint256); + function cycleTotalRedemptionRequests(uint256 cycle) external view returns (uint256); + function cycleDepositRequests(uint256 cycle, address user) external view returns (uint256); + function cycleRedemptionRequests(uint256 cycle, address user) external view returns (uint256); function lastActionCycle(address user) external view returns (uint256); - function cycleRebalancePrice(uint256 _cycleIndex) external view returns (uint256); -} \ No newline at end of file + function cycleRebalancePrice(uint256 cycle) external view returns (uint256); +} diff --git a/src/interfaces/IXToken.sol b/src/interfaces/IXToken.sol index 9a33de1..303f6eb 100644 --- a/src/interfaces/IXToken.sol +++ b/src/interfaces/IXToken.sol @@ -2,10 +2,13 @@ // author: bhargavaparoksham pragma solidity ^0.8.20; + +import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; import {IAssetOracle} from './IAssetOracle.sol'; -interface IXToken { - /** +interface IXToken is IERC20 { + + /** * @dev Thrown when a caller is not the pool contract */ error NotPool(); @@ -15,11 +18,6 @@ interface IXToken { */ error ZeroAddress(); - /** - * @dev Thrown when asset price is invalid (zero) - */ - error InvalidPrice(); - /** * @dev Thrown when account has insufficient balance for an operation */ @@ -34,44 +32,17 @@ interface IXToken { * @dev Emitted after the mint action * @param account The address receiving the minted tokens * @param value The amount being minted - * @param price The price at which the tokens are minted + * @param reserve The amount of reserve tokens which is backing the minted xTokens **/ - event Mint(address indexed account, uint256 value, uint256 price); + event Mint(address indexed account, uint256 value, uint256 reserve); /** * @dev Emitted after xTokens are burned * @param account The owner of the xTokens, getting burned * @param value The amount being burned + * @param reserve The amount of reserve tokens **/ - event Burn(address indexed account, uint256 value); - - /** - * @dev Returns the scaled balance of the user. The scaled balance represents the user's balance - * normalized by the underlying asset price, maintaining constant purchasing power. - * @param user The user whose balance is calculated - * @return The scaled balance of the user - **/ - function scaledBalanceOf(address user) external view returns (uint256); - - /** - * @dev Returns the scaled total supply of the token. Represents the total supply - * normalized by the asset price. - * @return The scaled total supply - **/ - function scaledTotalSupply() external view returns (uint256); - - /** - * @dev Returns the market value of a user's tokens. - * @param user The user whose balance is calculated - * @return The market value of a user's tokens - **/ - function marketValue(address user) external view returns (uint256); - - /** - * @dev Returns the total market value of all the tokens. - * @return The total market value of all the tokens - **/ - function totalMarketValue() external view returns (uint256); + event Burn(address indexed account, uint256 value, uint256 reserve); /** * @dev Returns the version of the xToken implementation @@ -79,37 +50,46 @@ interface IXToken { **/ function XTOKEN_VERSION() external view returns (uint256); - /** - * @dev Returns the oracle contract address used for price feeds - * @return The address of the oracle contract - **/ - function oracle() external view returns (IAssetOracle); - /** * @dev Returns the pool contract address that manages this token * @return The address of the pool contract **/ function pool() external view returns (address); + /** + * @dev Returns the reserve balance of the user that is backing the xTokens. + * @param user The user whose balance is calculated + * @return The reserve balance of the user + **/ + function reserveBalanceOf(address user) external view returns (uint256); + + /** + * @dev Returns the reserve total supply of the token. + * @return The reserve total supply + **/ + function totalReserveSupply() external view returns (uint256); + /** * @dev Mints `amount` xTokens to `account` * @param account The address receiving the minted tokens * @param amount The amount of tokens getting minted - * @param price The price at which the tokens are minted + * @param reserve The amount of reserve tokens which is backing the minted xTokens */ function mint( address account, uint256 amount, - uint256 price + uint256 reserve ) external; /** * @dev Burns xTokens from `account` * @param account The owner of the xTokens, getting burned * @param amount The amount being burned + * @param reserve The amount of reserve tokens **/ function burn( address account, - uint256 amount + uint256 amount, + uint256 reserve ) external; } \ No newline at end of file diff --git a/src/protocol/AssetPool.sol b/src/protocol/AssetPool.sol index 4dd20d1..c10d75f 100644 --- a/src/protocol/AssetPool.sol +++ b/src/protocol/AssetPool.sol @@ -10,12 +10,14 @@ import "openzeppelin-contracts/contracts/utils/math/Math.sol"; import {IAssetPool} from "../interfaces/IAssetPool.sol"; import {IXToken} from "../interfaces/IXToken.sol"; import {ILPRegistry} from "../interfaces/ILPRegistry.sol"; +import {IAssetOracle} from "../interfaces/IAssetOracle.sol"; import {xToken} from "./xToken.sol"; contract AssetPool is IAssetPool, Ownable, Pausable { IERC20 public immutable reserveToken; // Reserve token (e.g., USDC) IXToken public immutable assetToken; // xToken contract ILPRegistry public immutable lpRegistry; // LP Registry contract + IAssetOracle public immutable assetOracle; // Asset Oracle contract // Pool state uint256 public cycleIndex; @@ -28,10 +30,7 @@ contract AssetPool is IAssetPool, Ownable, Pausable { uint256 public rebalanceTime; // Asset states - uint256 public reserveBalance; // total reserve token balance - uint256 public totalDepositRequests; // Total deposit requests - uint256 public totalRedemptionRequests; // Total redemption requests - uint256 public totalRedemptionScaledRequests; // Total redemption requests in scaled amount + uint256 public totalReserveBalance; // total reserve token balance // Rebalancing instructions uint256 public newReserveSupply; // New reserve balance after rebalance excluding rebalance amount @@ -44,9 +43,11 @@ contract AssetPool is IAssetPool, Ownable, Pausable { mapping(address => bool) public hasRebalanced; // User request states - mapping(address => uint256) public depositRequests; // Pending deposits per user - mapping(address => uint256) public redemptionRequests; // Pending burns per user - mapping(address => uint256) public redemptionScaledRequests; // Pending burns per user in scaled amount + mapping(address => uint256) public reserveBalance; // User reserve balance + mapping(uint256 => uint256) public cycleTotalDepositRequests; // Pending deposits per user + mapping(uint256 => uint256) public cycleTotalRedemptionRequests; // Pending burns per user + mapping(uint256 => mapping(address => uint256)) public cycleDepositRequests; // Pending deposits per user + mapping(uint256 => mapping(address => uint256)) public cycleRedemptionRequests; // Pending burns per user mapping(address => uint256) public lastActionCycle; // Last cycle user interacted with mapping(uint256 => uint256) public cycleRebalancePrice; // price at which the rebalance was executed in a cycle @@ -58,18 +59,19 @@ contract AssetPool is IAssetPool, Ownable, Pausable { address _reserveToken, string memory _xTokenName, string memory _xTokenSymbol, - address _oracle, + address _assetOracle, address _lpRegistry, uint256 _cyclePeriod, uint256 _rebalancingPeriod, address _owner ) Ownable(_owner) { - if (_reserveToken == address(0) || _oracle == address(0) || _lpRegistry == address(0)) + if (_reserveToken == address(0) || _assetOracle == address(0) || _lpRegistry == address(0)) revert ZeroAddress(); reserveToken = IERC20(_reserveToken); - assetToken = new xToken(_xTokenName, _xTokenSymbol, _oracle); + assetToken = new xToken(_xTokenName, _xTokenSymbol); lpRegistry = ILPRegistry(_lpRegistry); + assetOracle = IAssetOracle(_assetOracle); cycleState = CycleState.ACTIVE; cycleTime = _cyclePeriod; rebalanceTime = _rebalancingPeriod; @@ -87,92 +89,88 @@ contract AssetPool is IAssetPool, Ownable, Pausable { // User Actions function depositReserve(uint256 amount) external whenNotPaused notRebalancing { + uint256 userCycle = lastActionCycle[msg.sender]; if (amount == 0) revert InvalidAmount(); - + if (userCycle > 0 && userCycle != cycleIndex) revert MintOrBurnPending(); reserveToken.transferFrom(msg.sender, address(this), amount); - reserveBalance += amount; - depositRequests[msg.sender] += amount; - totalDepositRequests += amount; + cycleDepositRequests[cycleIndex][msg.sender] += amount; + cycleTotalDepositRequests[cycleIndex] += amount; lastActionCycle[msg.sender] = cycleIndex; emit DepositRequested(msg.sender, amount, cycleIndex); } function cancelDeposit() external notRebalancing { - uint256 amount = depositRequests[msg.sender]; + uint256 amount = cycleDepositRequests[cycleIndex][msg.sender]; if (amount == 0) revert NothingToCancel(); - depositRequests[msg.sender] = 0; - totalDepositRequests -= amount; - reserveBalance -= amount; + cycleDepositRequests[cycleIndex][msg.sender] = 0; + cycleTotalDepositRequests[cycleIndex] -= amount; reserveToken.transfer(msg.sender, amount); + lastActionCycle[msg.sender] = 0; emit DepositCancelled(msg.sender, amount, cycleIndex); } function mintAsset(address user) external whenNotPaused notRebalancing { - if (lastActionCycle[user] >= cycleIndex) revert NothingToClaim(); - uint256 amount = depositRequests[user]; - if (amount == 0) revert NothingToClaim(); + uint256 userCycle = lastActionCycle[user]; + if (userCycle >= cycleIndex) revert NothingToClaim(); + uint256 reserveAmount = cycleDepositRequests[userCycle][msg.sender]; + if (reserveAmount == 0) revert NothingToClaim(); + + cycleDepositRequests[userCycle][msg.sender] = 0; + cycleTotalDepositRequests[userCycle] -= reserveAmount; + uint256 rebalancePrice = cycleRebalancePrice[userCycle]; + lastActionCycle[user] = 0; - depositRequests[user] = 0; - totalDepositRequests -= amount; - uint256 rebalancePrice = cycleRebalancePrice[lastActionCycle[user]]; + uint256 assetAmount = Math.mulDiv(reserveAmount, PRECISION, rebalancePrice); - assetToken.mint(user, amount, rebalancePrice); + assetToken.mint(user, assetAmount, reserveAmount); - emit AssetClaimed(msg.sender, amount, cycleIndex); + emit AssetClaimed(msg.sender, assetAmount, userCycle); } - function burnAsset(uint256 xTokenAmount) external whenNotPaused notRebalancing { - if (xTokenAmount == 0) revert InvalidAmount(); - if (assetToken.balanceOf(msg.sender) < xTokenAmount) revert InsufficientBalance(); + function burnAsset(uint256 assetAmount) external whenNotPaused notRebalancing { + uint256 userBalance = assetToken.balanceOf(msg.sender); + uint256 userCycle = lastActionCycle[msg.sender]; + if (assetAmount == 0) revert InvalidAmount(); + if (userBalance < assetAmount) revert InsufficientBalance(); + if (userCycle > 0 && userCycle != cycleIndex) revert MintOrBurnPending(); - // Get scaled amount from xToken contract - uint256 scaledAmount = assetToken.scaledBalanceOf(msg.sender); - uint256 balance = assetToken.balanceOf(msg.sender); - uint256 scaledBurnAmount = Math.mulDiv(scaledAmount, xTokenAmount, balance); - - assetToken.burn(msg.sender, xTokenAmount); - redemptionScaledRequests[msg.sender] = scaledBurnAmount; - totalRedemptionScaledRequests += scaledBurnAmount; - redemptionRequests[msg.sender] = xTokenAmount; - totalRedemptionRequests += xTokenAmount; + assetToken.transferFrom(msg.sender, address(this), assetAmount); + cycleRedemptionRequests[cycleIndex][msg.sender] = assetAmount; + cycleTotalRedemptionRequests[cycleIndex] += assetAmount; lastActionCycle[msg.sender] = cycleIndex; - emit BurnRequested(msg.sender, xTokenAmount, cycleIndex); + emit BurnRequested(msg.sender, assetAmount, cycleIndex); } function cancelBurn() external notRebalancing { - uint256 amount = redemptionRequests[msg.sender]; - if (amount == 0) revert NothingToCancel(); - - uint256 scaledAmount = redemptionScaledRequests[msg.sender]; - - totalRedemptionRequests -= amount; - totalRedemptionScaledRequests -= scaledAmount; + uint256 assetAmount = cycleRedemptionRequests[cycleIndex][msg.sender]; + if (assetAmount == 0) revert NothingToCancel(); - redemptionRequests[msg.sender] = 0; - redemptionScaledRequests[msg.sender] = 0; + cycleRedemptionRequests[cycleIndex][msg.sender] = 0; + cycleTotalRedemptionRequests[cycleIndex] -= assetAmount; + lastActionCycle[msg.sender] = 0; - uint256 price = Math.mulDiv(amount, PRECISION, scaledAmount); - assetToken.mint(msg.sender, amount, price); + assetToken.transfer(msg.sender, assetAmount); - emit BurnCancelled(msg.sender, amount, cycleIndex); + emit BurnCancelled(msg.sender, assetAmount, cycleIndex); } function withdrawReserve(address user) external whenNotPaused notRebalancing { - if (lastActionCycle[user] >= cycleIndex) revert NothingToClaim(); - uint256 amount = redemptionRequests[user]; - if (amount == 0) revert NothingToClaim(); + uint256 userCycle = lastActionCycle[user]; + if (userCycle >= cycleIndex) revert NothingToClaim(); + uint256 assetAmount = cycleRedemptionRequests[userCycle][msg.sender]; + if (assetAmount == 0) revert NothingToClaim(); - uint256 scaledAmount = redemptionScaledRequests[user]; - redemptionRequests[user] = 0; - redemptionScaledRequests[user] = 0; - uint256 reserveAmount = scaledAmount * cycleRebalancePrice[lastActionCycle[user]]; - reserveToken.transfer(user, reserveAmount); + uint256 reserveAmountToTransfer = Math.mulDiv(assetAmount, cycleRebalancePrice[userCycle], PRECISION); + cycleRedemptionRequests[userCycle][user] = 0; + cycleTotalRedemptionRequests[userCycle] -= assetAmount; + lastActionCycle[user] = 0; + reserveToken.transfer(user, reserveAmountToTransfer); - emit ReserveWithdrawn(user, reserveAmount, cycleIndex); + emit ReserveWithdrawn(user, reserveAmountToTransfer, cycleIndex); } // -------------------------------------------------------------------------------- @@ -187,16 +185,19 @@ contract AssetPool is IAssetPool, Ownable, Pausable { require(cycleState == CycleState.ACTIVE, "Already rebalancing"); require(block.timestamp < nextRebalanceStartDate, "Cycle inprogress"); cycleState = CycleState.REBALANCING; + uint256 assetPrice = assetOracle.assetPrice(); + uint256 depositRequests = cycleTotalDepositRequests[cycleIndex]; + uint256 redemptionRequestsInAsset = cycleTotalRedemptionRequests[cycleIndex]; + uint256 redemptionRequestsInReserve = Math.mulDiv(redemptionRequestsInAsset, assetPrice, PRECISION); + uint256 assetReserveSupplyInPool = assetToken.reserveBalanceOf(address(this)); - uint256 spotPrice = assetToken.oracle().assetPrice(); - netReserveDelta = int256(totalDepositRequests) - int256(totalRedemptionRequests); - newReserveSupply = assetToken.totalSupply() + totalDepositRequests; // Redemptions are already burned so they are not considered - newAssetSupply = Math.mulDiv(newReserveSupply, spotPrice, PRECISION); - rebalanceAmount = int256(Math.mulDiv(totalRedemptionScaledRequests, spotPrice, PRECISION)) - int256(totalRedemptionRequests); + netReserveDelta = int256(depositRequests) - int256(assetReserveSupplyInPool); + newReserveSupply = assetToken.totalReserveSupply() + depositRequests - assetReserveSupplyInPool; + rebalanceAmount = int256(redemptionRequestsInReserve) - int256(assetReserveSupplyInPool); emit RebalanceInitiated( cycleIndex, - spotPrice, + assetPrice, netReserveDelta, rebalanceAmount ); @@ -218,11 +219,9 @@ contract AssetPool is IAssetPool, Ownable, Pausable { if (isDeposit) { // If depositing stable, transfer from LP reserveToken.transferFrom(lp, address(this), amount); - reserveBalance += amount; } else { // If withdrawing stable, ensure the pool has enough - if (amount > reserveBalance) revert InvalidAmount(); - reserveBalance -= amount; + if (amount > uint256(rebalanceAmount)) revert InvalidAmount(); reserveToken.transfer(lp, amount); } @@ -274,7 +273,6 @@ contract AssetPool is IAssetPool, Ownable, Pausable { // View functions function getGeneralInfo() external view returns ( - uint256 _reserveBalance, uint256 _xTokenSupply, CycleState _cycleState, uint256 _cycleIndex, @@ -283,13 +281,12 @@ contract AssetPool is IAssetPool, Ownable, Pausable { uint256 _assetPrice ) { return ( - reserveBalance, assetToken.totalSupply(), cycleState, cycleIndex, nextRebalanceStartDate, nextRebalanceEndDate, - assetToken.oracle().assetPrice() + assetOracle.assetPrice() ); } @@ -303,8 +300,8 @@ contract AssetPool is IAssetPool, Ownable, Pausable { int256 _rebalanceAmount ) { - _totalDepositRequests = totalDepositRequests; - _totalRedemptionRequests = totalRedemptionRequests; + _totalDepositRequests = cycleTotalDepositRequests[cycleIndex]; + _totalRedemptionRequests = cycleTotalRedemptionRequests[cycleIndex]; _netReserveDelta = netReserveDelta; _rebalanceAmount = rebalanceAmount; } diff --git a/src/protocol/xToken.sol b/src/protocol/xToken.sol index dc828fb..6bf9c0d 100644 --- a/src/protocol/xToken.sol +++ b/src/protocol/xToken.sol @@ -6,19 +6,15 @@ pragma solidity ^0.8.20; import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol"; import "openzeppelin-contracts/contracts/utils/math/Math.sol"; -import "../interfaces/IAssetOracle.sol"; import "../interfaces/IXToken.sol"; /** * @title xToken Contract - * @notice This contract implements a price-scaling token that tracks an underlying real-world asset. - * @dev The token maintains scaled balances that adjust based on the underlying asset price. + * @notice This contract implements a token that tracks an underlying real-world asset. * All amounts are expected to be in 18 decimal precision. * The asset price is assumed to be in 18 decimal precision. */ contract xToken is IXToken, ERC20, ERC20Permit { - /// @notice Reference to the oracle providing asset price feeds - IAssetOracle public immutable oracle; /// @notice Address of the pool contract that manages this token address public immutable pool; @@ -29,11 +25,11 @@ contract xToken is IXToken, ERC20, ERC20Permit { /// @notice Price precision constant uint256 private constant PRECISION = 1e18; - /// @notice Mapping of scaled balances for each account - mapping(address => uint256) private _scaledBalances; + /// @notice Mapping of reserve balances for each account + mapping(address => uint256) private _reserveBalances; - /// @notice Total supply in scaled balance terms - uint256 private _totalScaledSupply; + /// @notice Total supply in reserve balance terms + uint256 private _totalReserveSupply; /** * @notice Ensures the caller is the pool contract @@ -47,64 +43,27 @@ contract xToken is IXToken, ERC20, ERC20Permit { * @notice Constructs the xToken contract * @param name The name of the token * @param symbol The symbol of the token - * @param _oracle The address of the asset price oracle */ - constructor(string memory name, string memory symbol, address _oracle) ERC20(name, symbol) ERC20Permit(name) { - if (_oracle == address(0)) revert ZeroAddress(); - oracle = IAssetOracle(_oracle); + constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) { pool = msg.sender; } - /** - * @notice Returns the scaled balance of an account - * @dev This balance is independent of the asset price and represents the user's share of the pool + /** + * @notice Returns the reserve balance of an account + * @dev This balance is independent of the asset price and represents the user's share of the reserve tokens in the pool * @param account The address of the account - * @return The scaled balance of the account - */ - function scaledBalanceOf(address account) public view returns (uint256) { - return _scaledBalances[account]; - } - - /** - * @notice Returns the total scaled supply - * @return The total scaled supply of tokens + * @return The reserve balance of the account */ - function scaledTotalSupply() public view returns (uint256) { - return _totalScaledSupply; + function reserveBalanceOf(address account) public view returns (uint256) { + return _reserveBalances[account]; } /** - * @notice Converts token amount to its scaled equivalent at the given price - * @param amount The amount to convert (in 18 decimal precision) - * @param price The asset price to use for conversion - * @return The equivalent scaled amount + * @notice Returns the total reserve supply + * @return The total reserve supply of tokens */ - function _convertToScaledAmountWithPrice(uint256 amount, uint256 price) internal pure returns (uint256) { - if (price == 0) revert InvalidPrice(); - return Math.mulDiv(amount, PRECISION, price); - } - - /** - * @notice Returns the market value of a user's tokens - * @dev Converts the scaled balance to market value using current asset price - * @param account The address of the account - * @return The market value of user's tokens in 18 decimal precision - */ - function marketValue(address account) public view returns (uint256) { - uint256 price = oracle.assetPrice(); - if (price == 0) revert InvalidPrice(); - return _scaledBalances[account] * price; - } - - /** - * @notice Returns the total market value of all tokens - * @dev Converts the total scaled supply to nominal terms using current asset price - * @return The total market value in 18 decimal precision - */ - function totalMarketValue() public view returns (uint256) { - uint256 price = oracle.assetPrice(); - if (price == 0) revert InvalidPrice(); - return _totalScaledSupply * price; + function totalReserveSupply() public view returns (uint256) { + return _totalReserveSupply; } /** @@ -112,14 +71,13 @@ contract xToken is IXToken, ERC20, ERC20Permit { * @dev Only callable by the pool contract * @param account The address receiving the minted tokens * @param amount The amount of tokens to mint (in 18 decimal precision) - * @param price The asset price at which the minting is done + * @param reserve The amount of reserve tokens which is backing the minted xTokens */ - function mint(address account, uint256 amount, uint256 price) external onlyPool { - uint256 scaledAmount = _convertToScaledAmountWithPrice(amount, price); - _scaledBalances[account] += scaledAmount; - _totalScaledSupply += scaledAmount; + function mint(address account, uint256 amount, uint256 reserve) external onlyPool { + _reserveBalances[account] += reserve; + _totalReserveSupply += reserve; _mint(account, amount); - emit Mint(account, amount, price); + emit Mint(account, amount, reserve); } /** @@ -127,17 +85,17 @@ contract xToken is IXToken, ERC20, ERC20Permit { * @dev Only callable by the pool contract * @param account The address to burn tokens from * @param amount The amount of tokens to burn (in 18 decimal precision) + * @param reserve The amount of reserve tokens to burn */ - function burn(address account, uint256 amount) external onlyPool { + function burn(address account, uint256 amount, uint256 reserve) external onlyPool { uint256 balance = balanceOf(account); + uint256 reserveBalance = _reserveBalances[account]; if (balance < amount) revert InsufficientBalance(); - uint256 scaledBalance = _scaledBalances[account]; - uint256 scaledBalanceToBurn = Math.mulDiv(scaledBalance, amount, balance); - - _scaledBalances[account] -= scaledBalanceToBurn; - _totalScaledSupply -= scaledBalanceToBurn; + if (reserveBalance < reserve) revert InsufficientBalance(); + _reserveBalances[account] -= reserve; + _totalReserveSupply -= reserve; _burn(account, amount); - emit Burn(account, amount); + emit Burn(account, amount, reserve); } /** @@ -146,16 +104,16 @@ contract xToken is IXToken, ERC20, ERC20Permit { * @param amount The amount of tokens to transfer (in 18 decimal precision) * @return success True if the transfer succeeded */ - function transfer(address recipient, uint256 amount) public override(ERC20) returns (bool) { + function transfer(address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) { if (recipient == address(0)) revert ZeroAddress(); uint256 balance = balanceOf(msg.sender); if (balance < amount) revert InsufficientBalance(); - uint256 scaledBalance = _scaledBalances[msg.sender]; - uint256 scaledBalanceToTransfer = Math.mulDiv(scaledBalance, amount, balance); + uint256 reserveBalance = _reserveBalances[msg.sender]; + uint256 reserveBalanceToTransfer = Math.mulDiv(reserveBalance, amount, balance); - _scaledBalances[msg.sender] -= scaledBalanceToTransfer; - _scaledBalances[recipient] += scaledBalanceToTransfer; + _reserveBalances[msg.sender] -= reserveBalanceToTransfer; + _reserveBalances[recipient] += reserveBalanceToTransfer; _transfer(msg.sender, recipient, amount); @@ -173,7 +131,7 @@ contract xToken is IXToken, ERC20, ERC20Permit { address sender, address recipient, uint256 amount - ) public override(ERC20) returns (bool) { + ) public override(ERC20, IERC20) returns (bool) { if (recipient == address(0)) revert ZeroAddress(); uint256 currentAllowance = allowance(sender, msg.sender); if (currentAllowance < amount) revert InsufficientAllowance(); @@ -181,11 +139,11 @@ contract xToken is IXToken, ERC20, ERC20Permit { uint256 balance = balanceOf(sender); if (balance < amount) revert InsufficientBalance(); - uint256 scaledBalance = _scaledBalances[sender]; - uint256 scaledBalanceToTransfer = scaledBalance * amount / balance; + uint256 reserveBalance = _reserveBalances[sender]; + uint256 reserveBalanceToTransfer = Math.mulDiv(reserveBalance, amount, balance); - _scaledBalances[sender] -= scaledBalanceToTransfer; - _scaledBalances[recipient] += scaledBalanceToTransfer; + _reserveBalances[sender] -= reserveBalanceToTransfer; + _reserveBalances[recipient] += reserveBalanceToTransfer; _approve(sender, msg.sender, currentAllowance - amount); _transfer(sender, recipient, amount);