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.
This commit is contained in:
326
contracts/reserve/ReserveSystem.sol
Normal file
326
contracts/reserve/ReserveSystem.sol
Normal file
@@ -0,0 +1,326 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user