Files
smom-dbis-138/contracts/reserve/StablecoinReserveVault.sol
defiQUG 76aa419320 feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.

Run forge build && forge test before deploy (large solc graph).

Made-with: Cursor
2026-04-07 23:40:52 -07:00

330 lines
13 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
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";
interface ICompliantTokenOwnerControl {
function owner() external view returns (address);
function pause() external;
function unpause() external;
function transferOwnership(address newOwner) external;
function mint(address to, uint256 amount) external;
function burn(uint256 amount) external;
function totalSupply() external view returns (uint256);
}
/**
* @title StablecoinReserveVault
* @notice 1:1 backing mechanism for CompliantUSDT and CompliantUSDC with official tokens
* @dev Locks official USDT/USDC, mints cUSDT/cUSDC 1:1. Can be deployed on Ethereum Mainnet
* or connected via cross-chain bridge to Chain 138 for minting.
*
* IMPORTANT: This contract should be deployed on Ethereum Mainnet where official USDT/USDC exist.
* For Chain 138 deployment, tokens would be bridged/locked via cross-chain infrastructure.
*/
contract StablecoinReserveVault is AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant RESERVE_OPERATOR_ROLE = keccak256("RESERVE_OPERATOR_ROLE");
bytes32 public constant REDEMPTION_OPERATOR_ROLE = keccak256("REDEMPTION_OPERATOR_ROLE");
// Official token addresses on Ethereum Mainnet
// These can be overridden in constructor for different networks
address public immutable officialUSDT;
address public immutable officialUSDC;
// Compliant token contracts (on Chain 138 or same network)
ICompliantTokenOwnerControl public immutable compliantUSDT;
ICompliantTokenOwnerControl public immutable compliantUSDC;
// Reserve tracking
uint256 public usdtReserveBalance;
uint256 public usdcReserveBalance;
// Total minted (for verification)
uint256 public totalCUSDTMinted;
uint256 public totalCUSDCMinted;
// Pause mechanism
bool public paused;
event ReserveDeposited(address indexed token, uint256 amount, address indexed depositor);
event ReserveWithdrawn(address indexed token, uint256 amount, address indexed recipient);
event CompliantTokensMinted(address indexed token, uint256 amount, address indexed recipient);
event CompliantTokensBurned(address indexed token, uint256 amount, address indexed redeemer);
event CompliantTokenPaused(address indexed token, address indexed operator);
event CompliantTokenUnpaused(address indexed token, address indexed operator);
event CompliantTokenOwnershipTransferred(address indexed token, address indexed newOwner);
event Paused(address indexed account);
event Unpaused(address indexed account);
modifier whenNotPaused() {
require(!paused, "StablecoinReserveVault: paused");
_;
}
/**
* @notice Constructor
* @param admin Admin address (will receive DEFAULT_ADMIN_ROLE)
* @param officialUSDT_ Official USDT token address (on Ethereum Mainnet: 0xdAC17F958D2ee523a2206206994597C13D831ec7)
* @param officialUSDC_ Official USDC token address (on Ethereum Mainnet: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)
* @param compliantUSDT_ CompliantUSDT contract address
* @param compliantUSDC_ CompliantUSDC contract address
*/
constructor(
address admin,
address officialUSDT_,
address officialUSDC_,
address compliantUSDT_,
address compliantUSDC_
) {
require(admin != address(0), "StablecoinReserveVault: zero admin");
require(officialUSDT_ != address(0), "StablecoinReserveVault: zero USDT");
require(officialUSDC_ != address(0), "StablecoinReserveVault: zero USDC");
require(compliantUSDT_ != address(0), "StablecoinReserveVault: zero cUSDT");
require(compliantUSDC_ != address(0), "StablecoinReserveVault: zero cUSDC");
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(RESERVE_OPERATOR_ROLE, admin);
_grantRole(REDEMPTION_OPERATOR_ROLE, admin);
officialUSDT = officialUSDT_;
officialUSDC = officialUSDC_;
compliantUSDT = ICompliantTokenOwnerControl(compliantUSDT_);
compliantUSDC = ICompliantTokenOwnerControl(compliantUSDC_);
}
/**
* @notice Deposit official USDT and mint cUSDT 1:1
* @dev Transfers USDT from caller, mints cUSDT to caller
* @param amount Amount of USDT to deposit (6 decimals)
*/
function depositUSDT(uint256 amount) external whenNotPaused nonReentrant {
require(amount > 0, "StablecoinReserveVault: zero amount");
// Transfer official USDT from caller
IERC20(officialUSDT).safeTransferFrom(msg.sender, address(this), amount);
// Update reserve
usdtReserveBalance += amount;
totalCUSDTMinted += amount;
// Mint cUSDT to caller
compliantUSDT.mint(msg.sender, amount);
emit ReserveDeposited(officialUSDT, amount, msg.sender);
emit CompliantTokensMinted(address(compliantUSDT), amount, msg.sender);
}
/**
* @notice Seed official USDT reserves without minting new cUSDT
* @dev Used to retrofit backing for pre-existing canonical supply
*/
function seedUSDTReserve(uint256 amount) external onlyRole(RESERVE_OPERATOR_ROLE) nonReentrant {
require(amount > 0, "StablecoinReserveVault: zero amount");
IERC20(officialUSDT).safeTransferFrom(msg.sender, address(this), amount);
usdtReserveBalance += amount;
emit ReserveDeposited(officialUSDT, amount, msg.sender);
}
/**
* @notice Deposit official USDC and mint cUSDC 1:1
* @dev Transfers USDC from caller, mints cUSDC to caller
* @param amount Amount of USDC to deposit (6 decimals)
*/
function depositUSDC(uint256 amount) external whenNotPaused nonReentrant {
require(amount > 0, "StablecoinReserveVault: zero amount");
// Transfer official USDC from caller
IERC20(officialUSDC).safeTransferFrom(msg.sender, address(this), amount);
// Update reserve
usdcReserveBalance += amount;
totalCUSDCMinted += amount;
// Mint cUSDC to caller
compliantUSDC.mint(msg.sender, amount);
emit ReserveDeposited(officialUSDC, amount, msg.sender);
emit CompliantTokensMinted(address(compliantUSDC), amount, msg.sender);
}
/**
* @notice Seed official USDC reserves without minting new cUSDC
* @dev Used to retrofit backing for pre-existing canonical supply
*/
function seedUSDCReserve(uint256 amount) external onlyRole(RESERVE_OPERATOR_ROLE) nonReentrant {
require(amount > 0, "StablecoinReserveVault: zero amount");
IERC20(officialUSDC).safeTransferFrom(msg.sender, address(this), amount);
usdcReserveBalance += amount;
emit ReserveDeposited(officialUSDC, amount, msg.sender);
}
/**
* @notice Redeem cUSDT for official USDT 1:1
* @dev Burns cUSDT from caller, transfers USDT to caller
* @param amount Amount of cUSDT to redeem (6 decimals)
*/
function redeemUSDT(uint256 amount) external whenNotPaused nonReentrant {
require(amount > 0, "StablecoinReserveVault: zero amount");
require(usdtReserveBalance >= amount, "StablecoinReserveVault: insufficient reserve");
// Pull cUSDT from the redeemer, then burn from the vault balance as token owner.
IERC20(address(compliantUSDT)).safeTransferFrom(msg.sender, address(this), amount);
compliantUSDT.burn(amount);
// Update reserve
usdtReserveBalance -= amount;
totalCUSDTMinted -= amount;
// Transfer official USDT to caller
IERC20(officialUSDT).safeTransfer(msg.sender, amount);
emit CompliantTokensBurned(address(compliantUSDT), amount, msg.sender);
emit ReserveWithdrawn(officialUSDT, amount, msg.sender);
}
/**
* @notice Redeem cUSDC for official USDC 1:1
* @dev Burns cUSDC from caller, transfers USDC to caller
* @param amount Amount of cUSDC to redeem (6 decimals)
*/
function redeemUSDC(uint256 amount) external whenNotPaused nonReentrant {
require(amount > 0, "StablecoinReserveVault: zero amount");
require(usdcReserveBalance >= amount, "StablecoinReserveVault: insufficient reserve");
// Pull cUSDC from the redeemer, then burn from the vault balance as token owner.
IERC20(address(compliantUSDC)).safeTransferFrom(msg.sender, address(this), amount);
compliantUSDC.burn(amount);
// Update reserve
usdcReserveBalance -= amount;
totalCUSDCMinted -= amount;
// Transfer official USDC to caller
IERC20(officialUSDC).safeTransfer(msg.sender, amount);
emit CompliantTokensBurned(address(compliantUSDC), amount, msg.sender);
emit ReserveWithdrawn(officialUSDC, amount, msg.sender);
}
/**
* @notice Get reserve backing ratio
* @param token Address of compliant token (cUSDT or cUSDC)
* @return reserveBalance Current reserve balance
* @return tokenSupply Current token supply
* @return backingRatio Backing ratio (10000 = 100%)
*/
function getBackingRatio(address token) external view returns (
uint256 reserveBalance,
uint256 tokenSupply,
uint256 backingRatio
) {
if (token == address(compliantUSDT)) {
reserveBalance = usdtReserveBalance;
tokenSupply = compliantUSDT.totalSupply();
} else if (token == address(compliantUSDC)) {
reserveBalance = usdcReserveBalance;
tokenSupply = compliantUSDC.totalSupply();
} else {
revert("StablecoinReserveVault: unsupported token");
}
backingRatio = tokenSupply > 0
? (reserveBalance * 10000) / tokenSupply
: 0;
}
/**
* @notice Check if reserves are adequate
* @return usdtAdequate True if USDT reserves are adequate
* @return usdcAdequate True if USDC reserves are adequate
*/
function checkReserveAdequacy() external view returns (bool usdtAdequate, bool usdcAdequate) {
uint256 cUSDTSupply = compliantUSDT.totalSupply();
uint256 cUSDCSupply = compliantUSDC.totalSupply();
usdtAdequate = usdtReserveBalance >= cUSDTSupply;
usdcAdequate = usdcReserveBalance >= cUSDCSupply;
}
/**
* @notice Pause all operations
*/
function pause() external onlyRole(DEFAULT_ADMIN_ROLE) {
paused = true;
emit Paused(msg.sender);
}
/**
* @notice Unpause all operations
*/
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
paused = false;
emit Unpaused(msg.sender);
}
/**
* @notice Pause one of the compliant tokens currently owned by the vault
*/
function pauseCompliantToken(address token) external onlyRole(DEFAULT_ADMIN_ROLE) {
_requireSupportedCompliantToken(token);
ICompliantTokenOwnerControl(token).pause();
emit CompliantTokenPaused(token, msg.sender);
}
/**
* @notice Unpause one of the compliant tokens currently owned by the vault
*/
function unpauseCompliantToken(address token) external onlyRole(DEFAULT_ADMIN_ROLE) {
_requireSupportedCompliantToken(token);
ICompliantTokenOwnerControl(token).unpause();
emit CompliantTokenUnpaused(token, msg.sender);
}
/**
* @notice Move compliant token ownership away from the vault if governance needs to recover control
*/
function transferCompliantTokenOwnership(
address token,
address newOwner
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(newOwner != address(0), "StablecoinReserveVault: zero new owner");
_requireSupportedCompliantToken(token);
ICompliantTokenOwnerControl(token).transferOwnership(newOwner);
emit CompliantTokenOwnershipTransferred(token, newOwner);
}
/**
* @notice Emergency withdrawal (admin only, after pause)
* @dev Can be used to recover funds in emergency situations
*/
function emergencyWithdraw(address token, uint256 amount, address recipient)
external
onlyRole(DEFAULT_ADMIN_ROLE)
whenPaused
{
require(recipient != address(0), "StablecoinReserveVault: zero recipient");
IERC20(token).safeTransfer(recipient, amount);
}
modifier whenPaused() {
require(paused, "StablecoinReserveVault: not paused");
_;
}
function _requireSupportedCompliantToken(address token) internal view {
require(
token == address(compliantUSDT) || token == address(compliantUSDC),
"StablecoinReserveVault: unsupported compliant token"
);
}
}