Files
smom-dbis-138/contracts/reserve/ReserveSystem.sol
defiQUG 1fb7266469 Add Oracle Aggregator and CCIP Integration
- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control.
- Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities.
- Created .gitmodules to include OpenZeppelin contracts as a submodule.
- Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment.
- Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks.
- Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring.
- Created scripts for resource import and usage validation across non-US regions.
- Added tests for CCIP error handling and integration to ensure robust functionality.
- Included various new files and directories for the orchestration portal and deployment scripts.
2025-12-12 14:57:48 -08:00

327 lines
12 KiB
Solidity

// 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 "./IReserveSystem.sol";
/**
* @title ReserveSystem
* @notice Core implementation of the GRU Reserve System
* @dev Manages reserves in multiple asset classes (XAU, digital assets, sovereign instruments)
* Implements XAU triangulation conversion algorithm and redemption mechanisms
*/
contract ReserveSystem is IReserveSystem, AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
// ============ Constants ============
bytes32 public constant RESERVE_MANAGER_ROLE = keccak256("RESERVE_MANAGER_ROLE");
bytes32 public constant PRICE_FEED_ROLE = keccak256("PRICE_FEED_ROLE");
bytes32 public constant CONVERSION_OPERATOR_ROLE = keccak256("CONVERSION_OPERATOR_ROLE");
// Conversion fee parameters (basis points: 10000 = 100%)
uint256 public constant BASE_FEE_BPS = 10; // 0.1%
uint256 public constant SLIPPAGE_FEE_BPS = 50; // 0.5% per 1% slippage
uint256 public constant LARGE_TRANSACTION_THRESHOLD = 1_000_000 * 1e18; // $1M
uint256 public constant LARGE_TRANSACTION_FEE_BPS = 5; // 0.05%
// Price staleness threshold (30 seconds)
uint256 public constant PRICE_STALENESS_THRESHOLD = 30;
// Maximum slippage (0.5% for liquid, 1.0% for less liquid)
uint256 public constant MAX_SLIPPAGE_BPS = 50; // 0.5%
// ============ Storage ============
struct Reserve {
address asset;
uint256 balance;
uint256 lastUpdated;
}
struct PriceFeed {
uint256 price;
uint256 timestamp;
bool isValid;
}
struct Conversion {
address sourceAsset;
address targetAsset;
uint256 sourceAmount;
uint256 targetAmount;
uint256 fees;
address[] path;
uint256 timestamp;
}
// Reserve tracking
mapping(address => uint256) public reserveBalances;
mapping(bytes32 => Reserve) public reserves;
bytes32[] public reserveIds;
// Price feeds
mapping(address => PriceFeed) public priceFeeds;
address[] public supportedAssets;
// Conversion tracking
mapping(bytes32 => Conversion) public conversions;
bytes32[] public conversionIds;
// Asset metadata
mapping(address => bool) public isSupportedAsset;
mapping(address => bool) public isLiquidAsset; // For slippage calculation
// ============ Constructor ============
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(RESERVE_MANAGER_ROLE, admin);
_grantRole(PRICE_FEED_ROLE, admin);
_grantRole(CONVERSION_OPERATOR_ROLE, admin);
}
// ============ Reserve Management ============
function depositReserve(
address asset,
uint256 amount
) external override onlyRole(RESERVE_MANAGER_ROLE) nonReentrant returns (bytes32 reserveId) {
require(asset != address(0), "ReserveSystem: zero address");
require(amount > 0, "ReserveSystem: zero amount");
require(isSupportedAsset[asset], "ReserveSystem: unsupported asset");
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
reserveId = keccak256(abi.encodePacked(asset, amount, block.timestamp, reserveIds.length));
reserves[reserveId] = Reserve({
asset: asset,
balance: amount,
lastUpdated: block.timestamp
});
reserveIds.push(reserveId);
reserveBalances[asset] += amount;
emit ReserveDeposited(asset, amount, msg.sender, reserveId);
}
function withdrawReserve(
address asset,
uint256 amount,
address recipient
) external override onlyRole(RESERVE_MANAGER_ROLE) nonReentrant returns (bytes32 reserveId) {
require(asset != address(0), "ReserveSystem: zero address");
require(recipient != address(0), "ReserveSystem: zero recipient");
require(amount > 0, "ReserveSystem: zero amount");
require(reserveBalances[asset] >= amount, "ReserveSystem: insufficient reserve");
reserveBalances[asset] -= amount;
IERC20(asset).safeTransfer(recipient, amount);
reserveId = keccak256(abi.encodePacked(asset, amount, block.timestamp, reserveIds.length));
emit ReserveWithdrawn(asset, amount, recipient, reserveId);
}
function getReserveBalance(address asset) external view override returns (uint256) {
return reserveBalances[asset];
}
function getReserveById(bytes32 reserveId) external view override returns (address asset, uint256 balance) {
Reserve memory reserve = reserves[reserveId];
return (reserve.asset, reserve.balance);
}
// ============ Conversion ============
function convertAssets(
address sourceAsset,
address targetAsset,
uint256 amount
) external override onlyRole(CONVERSION_OPERATOR_ROLE) nonReentrant returns (
bytes32 conversionId,
uint256 targetAmount,
uint256 fees
) {
require(sourceAsset != address(0), "ReserveSystem: zero source asset");
require(targetAsset != address(0), "ReserveSystem: zero target asset");
require(amount > 0, "ReserveSystem: zero amount");
require(isSupportedAsset[sourceAsset], "ReserveSystem: unsupported source asset");
require(isSupportedAsset[targetAsset], "ReserveSystem: unsupported target asset");
// Calculate conversion
(uint256 calculatedAmount, uint256 calculatedFees, address[] memory path) =
calculateConversion(sourceAsset, targetAsset, amount);
require(reserveBalances[targetAsset] >= calculatedAmount, "ReserveSystem: insufficient target reserve");
// Transfer source asset
IERC20(sourceAsset).safeTransferFrom(msg.sender, address(this), amount);
reserveBalances[sourceAsset] += amount;
// Transfer target asset
reserveBalances[targetAsset] -= calculatedAmount;
IERC20(targetAsset).safeTransfer(msg.sender, calculatedAmount);
conversionId = keccak256(abi.encodePacked(sourceAsset, targetAsset, amount, block.timestamp, conversionIds.length));
conversions[conversionId] = Conversion({
sourceAsset: sourceAsset,
targetAsset: targetAsset,
sourceAmount: amount,
targetAmount: calculatedAmount,
fees: calculatedFees,
path: path,
timestamp: block.timestamp
});
conversionIds.push(conversionId);
emit ConversionExecuted(sourceAsset, targetAsset, amount, calculatedAmount, conversionId, calculatedFees);
return (conversionId, calculatedAmount, calculatedFees);
}
function calculateConversion(
address sourceAsset,
address targetAsset,
uint256 amount
) public view override returns (
uint256 targetAmount,
uint256 fees,
address[] memory path
) {
require(sourceAsset != address(0), "ReserveSystem: zero source asset");
require(targetAsset != address(0), "ReserveSystem: zero target asset");
require(amount > 0, "ReserveSystem: zero amount");
// Get prices
(uint256 sourcePrice, uint256 sourceTimestamp) = getPrice(sourceAsset);
(uint256 targetPrice, uint256 targetTimestamp) = getPrice(targetAsset);
require(sourceTimestamp > 0 && targetTimestamp > 0, "ReserveSystem: price feed not available");
require(block.timestamp - sourceTimestamp <= PRICE_STALENESS_THRESHOLD, "ReserveSystem: stale source price");
require(block.timestamp - targetTimestamp <= PRICE_STALENESS_THRESHOLD, "ReserveSystem: stale target price");
// Direct conversion
targetAmount = (amount * targetPrice) / sourcePrice;
// Calculate fees
fees = calculateFees(targetAmount, 0); // No slippage for direct conversion
// Simple path (direct conversion)
path = new address[](2);
path[0] = sourceAsset;
path[1] = targetAsset;
return (targetAmount, fees, path);
}
function calculateFees(uint256 amount, uint256 slippageBps) internal pure returns (uint256) {
uint256 baseFee = (amount * BASE_FEE_BPS) / 10000;
uint256 slippageFee = 0;
if (slippageBps > 10) { // 0.1% threshold
slippageFee = (amount * slippageBps * SLIPPAGE_FEE_BPS) / 1000000;
}
uint256 largeTransactionFee = 0;
if (amount >= LARGE_TRANSACTION_THRESHOLD) {
largeTransactionFee = (amount * LARGE_TRANSACTION_FEE_BPS) / 10000;
}
return baseFee + slippageFee + largeTransactionFee;
}
// ============ Redemption ============
function redeem(
address asset,
uint256 amount,
address recipient
) external override onlyRole(RESERVE_MANAGER_ROLE) nonReentrant returns (bytes32 redemptionId) {
require(asset != address(0), "ReserveSystem: zero address");
require(recipient != address(0), "ReserveSystem: zero recipient");
require(amount > 0, "ReserveSystem: zero amount");
require(reserveBalances[asset] >= amount, "ReserveSystem: insufficient reserve");
reserveBalances[asset] -= amount;
IERC20(asset).safeTransfer(recipient, amount);
redemptionId = keccak256(abi.encodePacked(asset, amount, block.timestamp, conversionIds.length));
emit RedemptionExecuted(asset, amount, recipient, redemptionId);
}
// ============ Price Feeds ============
function updatePriceFeed(
address asset,
uint256 price,
uint256 timestamp
) external override onlyRole(PRICE_FEED_ROLE) {
require(asset != address(0), "ReserveSystem: zero address");
require(price > 0, "ReserveSystem: zero price");
require(timestamp <= block.timestamp, "ReserveSystem: future timestamp");
if (!isSupportedAsset[asset]) {
isSupportedAsset[asset] = true;
supportedAssets.push(asset);
}
priceFeeds[asset] = PriceFeed({
price: price,
timestamp: timestamp,
isValid: true
});
emit PriceFeedUpdated(asset, price, timestamp);
}
function getPrice(address asset) public view override returns (uint256 price, uint256 timestamp) {
PriceFeed memory feed = priceFeeds[asset];
require(feed.isValid, "ReserveSystem: price feed not available");
return (feed.price, feed.timestamp);
}
function getConversionPrice(
address sourceAsset,
address targetAsset
) external view override returns (uint256) {
(uint256 sourcePrice,) = getPrice(sourceAsset);
(uint256 targetPrice,) = getPrice(targetAsset);
return (targetPrice * 1e18) / sourcePrice; // Price in 18 decimals
}
// ============ Admin Functions ============
function addSupportedAsset(address asset, bool isLiquid) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(asset != address(0), "ReserveSystem: zero address");
isSupportedAsset[asset] = true;
isLiquidAsset[asset] = isLiquid;
if (!_isInArray(asset, supportedAssets)) {
supportedAssets.push(asset);
}
}
function removeSupportedAsset(address asset) external onlyRole(DEFAULT_ADMIN_ROLE) {
isSupportedAsset[asset] = false;
}
function _isInArray(address item, address[] memory array) internal pure returns (bool) {
for (uint256 i = 0; i < array.length; i++) {
if (array[i] == item) {
return true;
}
}
return false;
}
function getSupportedAssets() external view returns (address[] memory) {
return supportedAssets;
}
}