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);
}
}
}
Submitted on: 2025-10-04 12:03:35
Comments
Log in to comment.
No comments yet.