SmartUSD.sol — Core Stablecoin
ERC-20
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
interface ITreasury {
function collateralRatio() external view returns (uint256);
function depositCollateral(address token, uint256 amount) external;
function withdrawCollateral(address token, uint256 amount) external;
}
interface ISGov {
function mint(address to, uint256 amount) external;
function burn(address from, uint256 amount) external;
}
interface IAIController {
function targetCR() external view returns (uint256);
function isPaused() external view returns (bool);
}
/**
* @title SmartUSD
* @notice AI-powered algorithmic stablecoin pegged to $1.00
* @dev Dual-token model with SGOV volatility absorber
*/
contract SmartUSD is ERC20, AccessControl, ReentrancyGuard, Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant AI_CONTROLLER_ROLE = keccak256("AI_CONTROLLER_ROLE");
ITreasury public treasury;
ISGov public sgov;
IAIController public aiController;
IERC20 public usdc;
uint256 public constant PRECISION = 1e18;
uint256 public constant MIN_CR = 105; // 105% minimum
uint256 public constant MINT_FEE = 10; // 0.1% = 10 bps
uint256 public constant BURN_FEE = 30; // 0.3% = 30 bps
uint256 public constant SGOV_RATIO = 100; // 1% of collateral
uint256 public totalMinted;
uint256 public totalBurned;
mapping(address => uint256) public lastMintBlock;
mapping(address => uint256) public userCollateral;
event Minted(address indexed user, uint256 susdAmount, uint256 collateral, uint256 sgov);
event Burned(address indexed user, uint256 susdAmount, uint256 usdcReturned, uint256 sgov);
event AIControllerUpdated(address newController);
event CircuitBreakerTriggered(string reason);
constructor(
address _usdc,
address _treasury,
address _sgov,
address _aiController
) ERC20("SMART USD", "SUSD") {
usdc = IERC20(_usdc);
treasury = ITreasury(_treasury);
sgov = ISGov(_sgov);
aiController = IAIController(_aiController);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
/**
* @notice Mint SUSD by depositing USDC collateral
* @param usdcAmount Amount of USDC to deposit as collateral
*/
function mint(uint256 usdcAmount) external nonReentrant whenNotPaused {
require(!aiController.isPaused(), "AI: circuit breaker active");
require(usdcAmount >= 1e6, "Min mint: 1 USDC");
require(lastMintBlock[msg.sender] < block.number, "One mint per block");
uint256 targetCR = aiController.targetCR(); // e.g. 115
require(targetCR >= MIN_CR, "CR below minimum");
// Calculate SUSD to mint: collateral / CR * 100
uint256 susdToMint = (usdcAmount * 1e12 * 100) / targetCR;
// Calculate fee
uint256 fee = (susdToMint * MINT_FEE) / 10000;
uint256 netSusd = susdToMint - fee;
// Calculate SGOV to co-mint (1% of collateral value)
uint256 sgovToMint = (usdcAmount * SGOV_RATIO) / 10000;
// Transfer collateral to treasury
require(usdc.transferFrom(msg.sender, address(treasury), usdcAmount), "USDC transfer failed");
// Mint SUSD to user
_mint(msg.sender, netSusd);
// Co-mint SGOV to user
sgov.mint(msg.sender, sgovToMint * 1e12); // scale to 18 decimals
userCollateral[msg.sender] += usdcAmount;
totalMinted += netSusd;
lastMintBlock[msg.sender] = block.number;
emit Minted(msg.sender, netSusd, usdcAmount, sgovToMint * 1e12);
}
/**
* @notice Burn SUSD to redeem USDC collateral
* @param susdAmount Amount of SUSD to burn
*/
function burn(uint256 susdAmount) external nonReentrant whenNotPaused {
require(balanceOf(msg.sender) >= susdAmount, "Insufficient SUSD");
require(susdAmount >= 1e15, "Min burn: 0.001 SUSD");
uint256 targetCR = aiController.targetCR();
// Calculate USDC to return: susd * CR / 100, minus fee
uint256 usdcToReturn = (susdAmount / 1e12 * targetCR) / 100;
uint256 fee = (usdcToReturn * BURN_FEE) / 10000;
uint256 netUsdc = usdcToReturn - fee;
// Calculate SGOV to co-burn
uint256 sgovToBurn = (susdAmount * SGOV_RATIO) / 10000;
// Burn SUSD from user
_burn(msg.sender, susdAmount);
// Co-burn SGOV from user
sgov.burn(msg.sender, sgovToBurn);
// Release collateral from treasury
treasury.withdrawCollateral(address(usdc), netUsdc);
require(usdc.transfer(msg.sender, netUsdc), "USDC transfer failed");
totalBurned += susdAmount;
emit Burned(msg.sender, susdAmount, netUsdc, sgovToBurn);
}
// Admin functions
function triggerCircuitBreaker(string memory reason) external onlyRole(AI_CONTROLLER_ROLE) {
_pause();
emit CircuitBreakerTriggered(reason);
}
function resume() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
function updateAIController(address newController) external onlyRole(DEFAULT_ADMIN_ROLE) {
aiController = IAIController(newController);
emit AIControllerUpdated(newController);
}
}
SGov.sol — Governance & Volatility Absorber
ERC-20
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
/**
* @title SGov
* @notice Governance token and volatility absorber for SMART USD protocol
* @dev Stakers earn protocol fees and act as backstop insurance
*/
contract SGov is ERC20, AccessControl, ReentrancyGuard {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
uint256 public constant APY_BASE = 1840; // 18.4% APY in bps
uint256 public constant LOCK_PERIOD = 0; // flexible staking
struct StakeInfo {
uint256 amount;
uint256 timestamp;
uint256 rewardDebt;
}
mapping(address => StakeInfo) public stakes;
uint256 public totalStaked;
uint256 public accRewardPerShare;
uint256 public lastRewardBlock;
uint256 public rewardPerBlock; // USDC rewards per block
IERC20 public rewardToken; // USDC
event Staked(address indexed user, uint256 amount);
event Unstaked(address indexed user, uint256 amount);
event YieldClaimed(address indexed user, uint256 amount);
event VoteCast(address indexed voter, uint256 proposalId, bool support, uint256 weight);
constructor(address _rewardToken) ERC20("SMART GOV", "SGOV") {
rewardToken = IERC20(_rewardToken);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
function burn(address from, uint256 amount) external onlyRole(MINTER_ROLE) {
_burn(from, amount);
}
function stake(uint256 amount) external nonReentrant {
require(amount > 0, "Cannot stake 0");
require(balanceOf(msg.sender) >= amount, "Insufficient SGOV");
_updateRewards(msg.sender);
_transfer(msg.sender, address(this), amount);
stakes[msg.sender].amount += amount;
stakes[msg.sender].timestamp = block.timestamp;
totalStaked += amount;
emit Staked(msg.sender, amount);
}
function unstake(uint256 amount) external nonReentrant {
require(stakes[msg.sender].amount >= amount, "Insufficient staked");
_updateRewards(msg.sender);
claimYield();
stakes[msg.sender].amount -= amount;
totalStaked -= amount;
_transfer(address(this), msg.sender, amount);
emit Unstaked(msg.sender, amount);
}
function claimYield() public nonReentrant {
_updateRewards(msg.sender);
uint256 pending = _pendingReward(msg.sender);
if (pending > 0) {
stakes[msg.sender].rewardDebt = stakes[msg.sender].amount * accRewardPerShare / 1e12;
rewardToken.transfer(msg.sender, pending);
emit YieldClaimed(msg.sender, pending);
}
}
function _updateRewards(address user) internal {
if (totalStaked > 0) {
uint256 reward = (block.number - lastRewardBlock) * rewardPerBlock;
accRewardPerShare += reward * 1e12 / totalStaked;
}
lastRewardBlock = block.number;
stakes[user].rewardDebt = stakes[user].amount * accRewardPerShare / 1e12;
}
function _pendingReward(address user) internal view returns (uint256) {
if (stakes[user].amount == 0) return 0;
return (stakes[user].amount * accRewardPerShare / 1e12) - stakes[user].rewardDebt;
}
function stakedBalance(address user) external view returns (uint256) {
return stakes[user].amount;
}
function pendingYield(address user) external view returns (uint256) {
return _pendingReward(user);
}
// Governance voting weight = balance + staked
function votingPower(address user) external view returns (uint256) {
return balanceOf(user) + stakes[user].amount;
}
}