diff --git a/nest/script/DeployNestContracts.s.sol b/nest/script/DeployNestContracts.s.sol index c3a4009..2159664 100644 --- a/nest/script/DeployNestContracts.s.sol +++ b/nest/script/DeployNestContracts.s.sol @@ -13,6 +13,7 @@ import { NestStaking } from "../src/NestStaking.sol"; import { IComponentToken } from "../src/interfaces/IComponentToken.sol"; import { AggregateTokenProxy } from "../src/proxy/AggregateTokenProxy.sol"; import { NestStakingProxy } from "../src/proxy/NestStakingProxy.sol"; +import { pUSD } from "../src/token/pUSD.sol"; // Concrete implementation of ComponentToken contract ConcreteComponentToken is ComponentToken { @@ -35,15 +36,17 @@ contract ConcreteComponentToken is ComponentToken { contract DeployNestContracts is Script, Test { address private constant NEST_ADMIN_ADDRESS = 0xb015762405De8fD24d29A6e0799c12e0Ea81c1Ff; - address private constant PUSD_ADDRESS = 0xe644F07B1316f28a7F134998e021eA9f7135F351; - address private constant USDT_ADDRESS = 0x2413b8C79Ce60045882559f63d308aE3DFE0903d; function test() public { } function run() external { vm.startBroadcast(NEST_ADMIN_ADDRESS); - IComponentToken USDT = IComponentToken(USDT_ADDRESS); + // Deploy pUSD + pUSD pUSDToken = new pUSD(); + ERC1967Proxy pUSDProxy = + new ERC1967Proxy(address(pUSDToken), abi.encodeCall(pUSD.initialize, (NEST_ADMIN_ADDRESS))); + console2.log("pUSDProxy deployed to:", address(pUSDProxy)); // Deploy ConcreteComponentToken ConcreteComponentToken componentToken = new ConcreteComponentToken(); @@ -55,7 +58,7 @@ contract DeployNestContracts is Script, Test { NEST_ADMIN_ADDRESS, // owner "Banana", // name "BAN", // symbol - IERC20(USDT_ADDRESS), // asset token + IERC20(address(pUSDProxy)), // asset token false, // async deposit false // async redeem ) @@ -73,7 +76,7 @@ contract DeployNestContracts is Script, Test { NEST_ADMIN_ADDRESS, "Apple", "AAPL", - IComponentToken(PUSD_ADDRESS), + IComponentToken(address(pUSDProxy)), 1e17, // ask price 1e17 // bid price ) @@ -82,7 +85,7 @@ contract DeployNestContracts is Script, Test { console2.log("AggregateTokenProxy deployed to:", address(aggregateTokenProxy)); // Add new component tokens - AggregateToken(address(aggregateTokenProxy)).addComponentToken(USDT); + AggregateToken(address(aggregateTokenProxy)).addComponentToken(IComponentToken(address(pUSDProxy))); AggregateToken(address(aggregateTokenProxy)).addComponentToken(IComponentToken(address(componentTokenProxy))); // Deploy NestStaking diff --git a/nest/src/token/pUSD.sol b/nest/src/token/pUSD.sol new file mode 100644 index 0000000..f1c693f --- /dev/null +++ b/nest/src/token/pUSD.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol"; + +interface IVault { + + function enter(address from, address asset, uint256 assetAmount, address to, uint256 shareAmount) external; + function exit(address to, address asset, uint256 assetAmount, address from, uint256 shareAmount) external; + function transferFrom(address from, address to, uint256 amount) external returns (bool); + function approve(address spender, uint256 amount) external returns (bool); + +} + +/** + * @title pUSD + * @author Eugene Y. Q. Shen, Alp Guneysel + * @notice Unified Plume USD stablecoin + */ +contract PUSD is Initializable, ERC20Upgradeable, AccessControlUpgradeable, UUPSUpgradeable { + + using SafeTransferLib for ERC20; + + // ========== ROLES ========== + bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + bytes32 public constant VAULT_ADMIN_ROLE = keccak256("VAULT_ADMIN_ROLE"); + bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); + + // ========== STATE VARIABLES ========== + IVault public vault; + bool public paused; + + // ========== EVENTS ========== + event VaultChanged(address oldVault, address newVault); + event Paused(address account); + event Unpaused(address account); + + // ========== MODIFIERS ========== + modifier whenNotPaused() { + require(!paused, "PUSD: paused"); + _; + } + + // ========== CONSTRUCTOR & INITIALIZER ========== + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address _vault, address admin) external initializer { + __ERC20_init("", ""); // Empty strings since we override name() and symbol() + __AccessControl_init(); + __UUPSUpgradeable_init(); + + vault = IVault(_vault); + + _grantRole(DEFAULT_ADMIN_ROLE, admin); + _grantRole(UPGRADER_ROLE, admin); + _grantRole(MINTER_ROLE, admin); + _grantRole(BURNER_ROLE, admin); + _grantRole(VAULT_ADMIN_ROLE, admin); + _grantRole(PAUSER_ROLE, admin); + } + + // ========== METADATA OVERRIDES ========== + function decimals() public pure override returns (uint8) { + return 6; + } + + function name() public pure override returns (string memory) { + return "Plume USD"; + } + + function symbol() public pure override returns (string memory) { + return "pUSD"; + } + + // ========== ADMIN FUNCTIONS ========== + function setVault( + address newVault + ) external onlyRole(VAULT_ADMIN_ROLE) { + address oldVault = address(vault); + vault = IVault(newVault); + emit VaultChanged(oldVault, newVault); + } + + function pause() external onlyRole(PAUSER_ROLE) { + paused = true; + emit Paused(msg.sender); + } + + function unpause() external onlyRole(PAUSER_ROLE) { + paused = false; + emit Unpaused(msg.sender); + } + + // Required override for UUPSUpgradeable + function _authorizeUpgrade( + address newImplementation + ) internal override onlyRole(UPGRADER_ROLE) { } + + // ========== ERC20 OVERRIDES ========== + function transfer(address to, uint256 amount) public override whenNotPaused returns (bool) { + return vault.transferFrom(msg.sender, to, amount); + } + + function transferFrom(address from, address to, uint256 amount) public override whenNotPaused returns (bool) { + return vault.transferFrom(from, to, amount); + } + + function approve(address spender, uint256 amount) public override whenNotPaused returns (bool) { + bool success = super.approve(spender, amount); + vault.approve(spender, amount); + return success; + } + + function balanceOf( + address account + ) public view override returns (uint256) { + return vault.balanceOf(account); + } + + // ========== INTERFACE SUPPORT ========== + function supportsInterface( + bytes4 interfaceId + ) public view override(AccessControlUpgradeable) returns (bool) { + return super.supportsInterface(interfaceId); + } + +}