pUSDCWrapper

Description:

ERC20 token contract with Mintable, Burnable capabilities. Standard implementation for fungible tokens on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/*
 * pUSDC Wrapper (for GrowthVault_2025)
 *
 * - Mints ERC20 pUSDC (6 decimals) 1:1 against USDC balance locked in GrowthVault_2025.
 * - Only vault owner can mint, capped by vault.getBalance().
 * - After unlock, vault owner funds settlement USDC into this wrapper.
 * - Holders can redeem pUSDC 1:1 for USDC.
 */

/* -------------------- Interfaces -------------------- */
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function allowance(address owner_, address spender) external view returns (uint256);

    function transfer(address to, uint256 value) external returns (bool);
    function approve(address spender, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner_, address indexed spender, uint256 value);
}

interface IGrowthVault {
    function owner() external view returns (address);
    function unlockTime() external view returns (uint256);
    function getBalance() external view returns (uint256);
}

/* -------------------- SafeERC20 -------------------- */
library Address {
    function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.call(data);
        require(success, errorMessage);
        return returndata;
    }
}

library SafeERC20 {
    using Address for address;

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, value);
        bytes memory ret = address(token).functionCall(data, "SafeERC20: transfer failed");
        if (ret.length > 0) require(abi.decode(ret, (bool)), "SafeERC20: transfer false");
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, value);
        bytes memory ret = address(token).functionCall(data, "SafeERC20: transferFrom failed");
        if (ret.length > 0) require(abi.decode(ret, (bool)), "SafeERC20: transferFrom false");
    }
}

/* -------------------- Minimal ERC20 (6 decimals) -------------------- */
contract ERC20_6 {
    string public name;
    string public symbol;

    uint8 public constant DECIMALS = 6;
    uint256 public _totalSupply;

    mapping(address => uint256) public _balances;
    mapping(address => mapping(address => uint256)) public _allowances;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner_, address indexed spender, uint256 value);

    constructor(string memory name_, string memory symbol_) {
        name = name_;
        symbol = symbol_;
    }

    function decimals() public pure returns (uint8) { return DECIMALS; }
    function totalSupply() public view returns (uint256) { return _totalSupply; }
    function balanceOf(address account) public view returns (uint256) { return _balances[account]; }
    function allowance(address owner_, address spender) public view returns (uint256) { return _allowances[owner_][spender]; }

    function transfer(address to, uint256 value) public returns (bool) {
        _transfer(msg.sender, to, value);
        return true;
    }

    function approve(address spender, uint256 value) public returns (bool) {
        _allowances[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }

    function transferFrom(address from, address to, uint256 value) public returns (bool) {
        uint256 allowed = _allowances[from][msg.sender];
        require(allowed >= value, "ERC20: insufficient allowance");
        unchecked { _allowances[from][msg.sender] = allowed - value; }
        _transfer(from, to, value);
        return true;
    }

    function _transfer(address from, address to, uint256 value) internal {
        require(to != address(0), "ERC20: to=0");
        uint256 bal = _balances[from];
        require(bal >= value, "ERC20: insufficient balance");
        unchecked {
            _balances[from] = bal - value;
            _balances[to] += value;
        }
        emit Transfer(from, to, value);
    }

    function _mint(address to, uint256 value) internal {
        require(to != address(0), "ERC20: mint to=0");
        _totalSupply += value;
        _balances[to] += value;
        emit Transfer(address(0), to, value);
    }

    function _burn(address from, uint256 value) internal {
        uint256 bal = _balances[from];
        require(bal >= value, "ERC20: burn exceeds balance");
        unchecked {
            _balances[from] = bal - value;
            _totalSupply -= value;
        }
        emit Transfer(from, address(0), value);
    }
}

/* -------------------- pUSDC Wrapper -------------------- */
contract pUSDCWrapper is ERC20_6 {
    using SafeERC20 for IERC20;

    IGrowthVault public immutable vault;   // GrowthVault_2025
    IERC20      public immutable usdc;    // Underlying USDC (6 decimals)
    address     public immutable vaultOwner;
    uint256     public immutable unlockTime;

    uint256 public totalSettled;

    event Minted(address indexed to, uint256 amount);
    event FundedSettlement(address indexed from, uint256 amount);
    event Redeemed(address indexed redeemer, uint256 amount);
    event Swept(address indexed to, uint256 amount);

    constructor(address _vault, address _usdc)
        ERC20_6("Pegged USDC (Locked Vault)", "pUSDC")
    {
        require(_vault != address(0) && _usdc != address(0), "Zero address");
        vault = IGrowthVault(_vault);
        usdc = IERC20(_usdc);

        vaultOwner = IGrowthVault(_vault).owner();
        unlockTime = IGrowthVault(_vault).unlockTime();
        require(unlockTime > block.timestamp, "Vault already unlocked");
    }

    function isUnlocked() public view returns (bool) {
        return block.timestamp >= unlockTime;
    }

    function maxMintable() public view returns (uint256) {
        uint256 bal = vault.getBalance();
        uint256 supply = totalSupply();
        return bal > supply ? (bal - supply) : 0;
    }

    function settlementCoverage() public view returns (uint256) {
        return usdc.balanceOf(address(this));
    }

    function mint(address to, uint256 amount) external {
        require(msg.sender == vaultOwner, "Not vault owner");
        require(amount > 0, "Amount=0");
        require(amount <= maxMintable(), "Exceeds cap");
        _mint(to, amount);
        emit Minted(to, amount);
    }

    function burn(uint256 amount) external {
        _burn(msg.sender, amount);
    }

    function burnFrom(address from, uint256 amount) external {
        uint256 allowed = allowance(from, msg.sender);
        require(allowed >= amount, "Burn exceeds allowance");
        unchecked { _allowances[from][msg.sender] = allowed - amount; }
        _burn(from, amount);
    }

    function fundSettlement(uint256 amount) external {
        require(isUnlocked(), "Not unlocked");
        require(amount > 0, "Amount=0");
        usdc.safeTransferFrom(msg.sender, address(this), amount);
        totalSettled += amount;
        emit FundedSettlement(msg.sender, amount);
    }

    function redeem(uint256 amount) external {
        require(isUnlocked(), "Not unlocked");
        require(amount > 0, "Amount=0");
        require(settlementCoverage() >= amount, "Insufficient settlement USDC");
        _burn(msg.sender, amount);
        SafeERC20.safeTransfer(usdc, msg.sender, amount);
        emit Redeemed(msg.sender, amount);
    }

    function sweepExcess(address to) external {
        require(msg.sender == vaultOwner, "Not vault owner");
        require(isUnlocked(), "Not unlocked");
        require(totalSupply() == 0, "Outstanding pUSDC");
        uint256 bal = usdc.balanceOf(address(this));
        if (bal > 0) {
            SafeERC20.safeTransfer(usdc, to, bal);
            emit Swept(to, bal);
        }
    }
}

Tags:
ERC20, Token, Mintable, Burnable|addr:0x79b6372de4b22ae3cfc53b56411c406dd19cc04c|verified:true|block:23503417|tx:0x701827e64a68e597cc765827d9aa1f79f875e5f224dc011cbaaa3585dbef1ed9|first_check:1759572215

Submitted on: 2025-10-04 12:03:35

Comments

Log in to comment.

No comments yet.