// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "../emoney/interfaces/IeMoneyToken.sol"; import "../emoney/interfaces/ITokenFactory138.sol"; import "./IReserveSystem.sol"; /** * @title ReserveTokenIntegration * @notice Integrates Reserve System with eMoney Token Factory * @dev Enables eMoney tokens to be backed by reserves and converted via the reserve system */ contract ReserveTokenIntegration is AccessControl, ReentrancyGuard { using SafeERC20 for IERC20; bytes32 public constant INTEGRATION_OPERATOR_ROLE = keccak256("INTEGRATION_OPERATOR_ROLE"); IReserveSystem public reserveSystem; ITokenFactory138 public tokenFactory; // Token to reserve asset mapping mapping(address => address) public tokenReserveAsset; // Reserve asset to token mapping mapping(address => address) public reserveAssetToken; // Reserve backing ratio (basis points: 10000 = 100%) mapping(address => uint256) public reserveBackingRatio; event TokenBackedByReserve( address indexed token, address indexed reserveAsset, uint256 backingRatio ); event ReserveBackingUpdated( address indexed token, address indexed reserveAsset, uint256 newBackingRatio ); event TokenConvertedViaReserve( address indexed sourceToken, address indexed targetToken, uint256 sourceAmount, uint256 targetAmount, bytes32 conversionId ); constructor( address admin, address reserveSystem_, address tokenFactory_ ) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(INTEGRATION_OPERATOR_ROLE, admin); reserveSystem = IReserveSystem(reserveSystem_); tokenFactory = ITokenFactory138(tokenFactory_); } /** * @notice Set reserve asset backing for an eMoney token * @param token Address of the eMoney token * @param reserveAsset Address of the reserve asset * @param backingRatio Backing ratio in basis points (10000 = 100%) */ function setTokenBacking( address token, address reserveAsset, uint256 backingRatio ) external onlyRole(INTEGRATION_OPERATOR_ROLE) { require(token != address(0), "ReserveTokenIntegration: zero token"); require(reserveAsset != address(0), "ReserveTokenIntegration: zero reserve asset"); require(backingRatio <= 10000, "ReserveTokenIntegration: invalid backing ratio"); tokenReserveAsset[token] = reserveAsset; reserveAssetToken[reserveAsset] = token; reserveBackingRatio[token] = backingRatio; emit TokenBackedByReserve(token, reserveAsset, backingRatio); } /** * @notice Update reserve backing ratio for a token * @param token Address of the eMoney token * @param newBackingRatio New backing ratio in basis points */ function updateBackingRatio( address token, uint256 newBackingRatio ) external onlyRole(INTEGRATION_OPERATOR_ROLE) { require(token != address(0), "ReserveTokenIntegration: zero token"); require(newBackingRatio <= 10000, "ReserveTokenIntegration: invalid backing ratio"); require(tokenReserveAsset[token] != address(0), "ReserveTokenIntegration: token not backed"); address reserveAsset = tokenReserveAsset[token]; reserveBackingRatio[token] = newBackingRatio; emit ReserveBackingUpdated(token, reserveAsset, newBackingRatio); } /** * @notice Convert eMoney tokens via reserve system * @param sourceToken Address of the source eMoney token * @param targetToken Address of the target eMoney token * @param amount Amount of source tokens to convert * @return targetAmount Amount of target tokens received * @return conversionId Conversion ID from reserve system */ function convertTokensViaReserve( address sourceToken, address targetToken, uint256 amount ) external nonReentrant returns (uint256 targetAmount, bytes32 conversionId) { require(sourceToken != address(0), "ReserveTokenIntegration: zero source token"); require(targetToken != address(0), "ReserveTokenIntegration: zero target token"); require(amount > 0, "ReserveTokenIntegration: zero amount"); address sourceReserveAsset = tokenReserveAsset[sourceToken]; address targetReserveAsset = tokenReserveAsset[targetToken]; require(sourceReserveAsset != address(0), "ReserveTokenIntegration: source not backed"); require(targetReserveAsset != address(0), "ReserveTokenIntegration: target not backed"); // Burn source tokens IeMoneyToken(sourceToken).burn(msg.sender, amount, "0x00"); // Calculate reserve asset amount based on backing ratio uint256 sourceReserveAmount = (amount * reserveBackingRatio[sourceToken]) / 10000; // Convert via reserve system uint256 targetReserveAmount; uint256 fees; (conversionId, targetReserveAmount, fees) = reserveSystem.convertAssets( sourceReserveAsset, targetReserveAsset, sourceReserveAmount ); // Calculate target token amount based on backing ratio targetAmount = (targetReserveAmount * 10000) / reserveBackingRatio[targetToken]; // Mint target tokens IeMoneyToken(targetToken).mint(msg.sender, targetAmount, bytes32(0)); emit TokenConvertedViaReserve(sourceToken, targetToken, amount, targetAmount, conversionId); return (targetAmount, conversionId); } /** * @notice Get reserve backing information for a token * @param token Address of the eMoney token * @return reserveAsset Address of the reserve asset * @return backingRatio Backing ratio in basis points * @return reserveBalance Current reserve balance */ function getTokenBacking(address token) external view returns ( address reserveAsset, uint256 backingRatio, uint256 reserveBalance ) { reserveAsset = tokenReserveAsset[token]; backingRatio = reserveBackingRatio[token]; if (reserveAsset != address(0)) { reserveBalance = reserveSystem.getReserveBalance(reserveAsset); } } /** * @notice Check if token has adequate reserve backing * @param token Address of the eMoney token * @return isAdequate True if reserves are adequate * @return requiredReserve Required reserve amount * @return currentReserve Current reserve amount */ function checkReserveAdequacy(address token) external view returns ( bool isAdequate, uint256 requiredReserve, uint256 currentReserve ) { address reserveAsset = tokenReserveAsset[token]; require(reserveAsset != address(0), "ReserveTokenIntegration: token not backed"); uint256 totalSupply = IERC20(token).totalSupply(); uint256 backingRatio = reserveBackingRatio[token]; requiredReserve = (totalSupply * backingRatio) / 10000; currentReserve = reserveSystem.getReserveBalance(reserveAsset); isAdequate = currentReserve >= requiredReserve; } }