Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NES-255] make pUSD a ComponentToken #97

Merged
merged 68 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
2131910
[NES-254] add adapter contract for pUSD
eyqs Nov 13, 2024
e415bc9
add vault for pUSD
ungaro Nov 13, 2024
51e5d53
remove incorrect include
ungaro Nov 13, 2024
9656816
keep pUSD as ERC20 and remove unnecessary functions
ungaro Nov 13, 2024
69467dd
remove comment
ungaro Nov 13, 2024
35ca154
fmt
ungaro Nov 13, 2024
c613ac9
remove hook
ungaro Nov 13, 2024
697c4f9
make pUSD a ComponentToken
ungaro Nov 13, 2024
03dcdd1
make pUSD a ComponentToken and update Deployment Script
ungaro Nov 13, 2024
2452ef9
change storage location
ungaro Nov 13, 2024
9fc98c6
remove erc4626
ungaro Nov 13, 2024
1636d22
update pUSD storage slot
ungaro Nov 13, 2024
300f1b4
pUSD is Initializable, ERC20Upgradeable, AccessControlUpgradeable, UU…
ungaro Nov 13, 2024
9e4f883
merge main
ungaro Nov 13, 2024
00cd67b
fix overrides
ungaro Nov 13, 2024
270dd48
update description
ungaro Nov 13, 2024
5bc1b2f
add test for pUSD - WIP
ungaro Nov 13, 2024
1ce0e5a
add mockvault for pUSD
ungaro Nov 13, 2024
47cb601
change initialization modifier to onlyInitializing
ungaro Nov 14, 2024
f5773db
remove pausable
ungaro Nov 14, 2024
b00649d
finish preliminary tests, fix some bugs with MockVault and pUSD
ungaro Nov 14, 2024
013f0eb
93.9% coverage for pUSD - rest is just assembly
ungaro Nov 14, 2024
f034359
remove comments
ungaro Nov 14, 2024
5521e69
add IVault interface
ungaro Nov 14, 2024
2ab5bb8
forge fmt
ungaro Nov 14, 2024
4fdd93f
better handling of convertToShares, convertToAssets, make tests 100% …
ungaro Nov 14, 2024
030869c
forge fmt
ungaro Nov 14, 2024
ff2f659
add extra comment
ungaro Nov 14, 2024
93a2f34
add reentrancy check
ungaro Nov 14, 2024
269841d
implementing some audit findings & ComponentToken precision test checks
ungaro Nov 14, 2024
396cff3
leave conversion functions unimplemented and force integrators to imp…
ungaro Nov 14, 2024
d6537de
add NatSpec comments for functions
ungaro Nov 14, 2024
d5d2a61
remove componenttoken comments
ungaro Nov 14, 2024
999adcb
deployment script for pUSD
ungaro Nov 14, 2024
114516a
add pUSD contract update
ungaro Nov 15, 2024
5e1f0cf
add more tests from pUSD
ungaro Nov 18, 2024
354bc4d
forge install: solmate
ungaro Nov 18, 2024
9093df0
change vault to teller
ungaro Nov 18, 2024
5d827b6
change deploy&upgrade pUSD scripts, update componenttoken _authorizeU…
ungaro Nov 18, 2024
7ba1fe4
change deposit and redeem functions
ungaro Nov 19, 2024
9dae01f
final changes, tests etc.
ungaro Nov 19, 2024
3111677
confirm teller.deposit works, add working test-case on-chain (pUSDPlu…
ungaro Nov 20, 2024
e0f73a8
redeem works through AtomicRequest, change proxy comments
ungaro Nov 21, 2024
e2a9c25
add boringvault struct, change related deployment and upgrade address…
ungaro Nov 22, 2024
bb540e8
fix tests and deployment/upgrade scripts
ungaro Nov 22, 2024
38608c3
remove pUSD deployment from DeployNestContracts
ungaro Nov 22, 2024
200ac3d
remove setBeforeTransferHook from mockvault
ungaro Nov 22, 2024
1deb5b9
fix conflicts
ungaro Nov 22, 2024
bb8000c
fix conflict that doesn't exist
ungaro Nov 22, 2024
fc49f7c
100% coverage on pUSD + added missing mocks
ungaro Nov 22, 2024
72b82cd
add pragma to MockUSDC
ungaro Nov 22, 2024
d2a1785
run everything without errors even RPC address is not provided for on…
ungaro Nov 25, 2024
db3516c
change balanceof to return vault's balance
ungaro Nov 25, 2024
0a0a9a8
change balanceOf, previewDeposit and previewRedeem functions
ungaro Nov 25, 2024
761855b
add decimals to interface
ungaro Nov 27, 2024
c270097
passing tests
ungaro Nov 29, 2024
0ea586a
add usdc & usdt, update deploy scripts, add missing tests
ungaro Dec 1, 2024
fcb7a30
forge fmt
ungaro Dec 1, 2024
d85a529
add IAccountantWithRateProviders, IRateProvider, Ilens and mock contr…
ungaro Dec 1, 2024
e152ec0
add lens and accountant
ungaro Dec 1, 2024
34b4546
balanceof and balanceofinassets
ungaro Dec 1, 2024
e8cbe26
all tests pass
ungaro Dec 1, 2024
145244a
100% coverage for pUSD, real calculations for converttoshares, conver…
ungaro Dec 1, 2024
4ed12dc
add nonreentrant
ungaro Dec 1, 2024
723a392
add assetsof
ungaro Dec 1, 2024
ee714c1
balanceOfInAssets to be renamed assetsof
ungaro Dec 1, 2024
8ae47f0
merge main
ungaro Dec 1, 2024
869fd2f
formatting
ungaro Dec 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions nest/script/DeployNestContracts.s.sol
ungaro marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ contract ConcreteComponentToken is ComponentToken {
contract DeployNestContracts is Script, Test {

address private constant NEST_ADMIN_ADDRESS = 0xb015762405De8fD24d29A6e0799c12e0Ea81c1Ff;
address private constant USDC_ADDRESS = 0x401eCb1D350407f13ba348573E5630B83638E30D;
address private constant VAULT_ADDRESS = 0x52805adf7b3d25c013eDa66eF32b53d1696f809C;

function test() public { }

Expand All @@ -44,8 +46,11 @@ contract DeployNestContracts is Script, Test {

// Deploy pUSD
pUSD pUSDToken = new pUSD();
ERC1967Proxy pUSDProxy =
new ERC1967Proxy(address(pUSDToken), abi.encodeCall(pUSD.initialize, (NEST_ADMIN_ADDRESS)));
ERC1967Proxy pUSDProxy = new ERC1967Proxy(
address(pUSDToken),
abi.encodeCall(pUSD.initialize, (NEST_ADMIN_ADDRESS, IERC20(USDC_ADDRESS), VAULT_ADDRESS))
);

ungaro marked this conversation as resolved.
Show resolved Hide resolved
console2.log("pUSDProxy deployed to:", address(pUSDProxy));

// Deploy ConcreteComponentToken
Expand Down
73 changes: 73 additions & 0 deletions nest/src/mocks/MockVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract MockVault {
ungaro marked this conversation as resolved.
Show resolved Hide resolved
using SafeERC20 for IERC20;

mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
IERC20 public asset;

function enter(
address from,
address asset_,
uint256 assetAmount,
address to,
uint256 shareAmount
) external {
if (assetAmount > 0) {
IERC20(asset_).safeTransferFrom(from, address(this), assetAmount);
}
_balances[to] = _balances[to] + shareAmount;
}

function exit(
address to,
address asset_,
uint256 assetAmount,
address from,
uint256 shareAmount
) external {
require(_balances[from] >= shareAmount, "MockVault: insufficient balance");
_balances[from] = _balances[from] - shareAmount;

if (assetAmount > 0) {
IERC20(asset_).safeTransfer(to, assetAmount);
}
}

function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool) {
require(_balances[from] >= amount, "MockVault: insufficient balance");

uint256 allowed = _allowances[from][msg.sender];
if (allowed != type(uint256).max) {
require(allowed >= amount, "MockVault: insufficient allowance");
_allowances[from][msg.sender] = allowed - amount;
}

_balances[from] = _balances[from] - amount;
_balances[to] = _balances[to] + amount;
return true;
}

function approve(address spender, uint256 amount) external returns (bool) {
_allowances[msg.sender][spender] = amount;
return true;
}

function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}

function setBeforeTransferHook(address) external {
// Mock implementation
}
}
179 changes: 115 additions & 64 deletions nest/src/token/pUSD.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@
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";
import { ERC4626Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";

import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

import { ComponentToken } from "../ComponentToken.sol";
import { IComponentToken } from "../interfaces/IComponentToken.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);
function balanceOf(
address account
) external view returns (uint256);

}

Expand All @@ -22,115 +31,157 @@ interface IVault {
* @author Eugene Y. Q. Shen, Alp Guneysel
* @notice Unified Plume USD stablecoin
*/
contract PUSD is Initializable, ERC20Upgradeable, AccessControlUpgradeable, UUPSUpgradeable {

using SafeTransferLib for ERC20;
contract pUSD is Initializable, ERC20Upgradeable, AccessControlUpgradeable, UUPSUpgradeable, ComponentToken {

// ========== STORAGE ==========
/// @custom:storage-location erc7201:plume.storage.pUSD
struct pUSDStorage {
IVault vault;
uint8 tokenDecimals;
bool paused;
string tokenName;
string tokenSymbol;
}

// ========== 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");
// keccak256(abi.encode(uint256(keccak256("plume.storage.pUSD")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PUSD_STORAGE_LOCATION = 0x54ae4f9578cdf7faaee986bff2a08b358f01b852b4da3af4f67309dae312ee00;

// ========== STATE VARIABLES ==========
IVault public vault;
bool public paused;
function _getpUSDStorage() private pure returns (pUSDStorage storage $) {
bytes32 position = PUSD_STORAGE_LOCATION;
assembly {
$.slot := position
}
}

// ========== EVENTS ==========
event VaultChanged(address oldVault, address newVault);
event Paused(address account);
event Unpaused(address account);

// ========== ROLES ==========
bytes32 public constant VAULT_ADMIN_ROLE = keccak256("VAULT_ADMIN_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

// ========== MODIFIERS ==========
modifier whenNotPaused() {
require(!paused, "PUSD: paused");
require(!_getpUSDStorage().paused, "pUSD: paused");
_;
}

// ========== CONSTRUCTOR & INITIALIZER ==========
/// @custom:oz-upgrades-unsafe-allow constructor
/**
* @notice Prevent the implementation contract from being initialized or reinitialized
* @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";
/**
* @notice Initialize pUSD
* @param owner Address of the owner of pUSD
* @param asset_ Address of the underlying asset
* @param vault_ Address of the Boring Vault
*/
function initialize(address owner, IERC20 asset_, address vault_) public initializer {
ComponentToken.initialize(owner, "", "", asset_, false, false);
ungaro marked this conversation as resolved.
Show resolved Hide resolved

pUSDStorage storage $ = _getpUSDStorage();
$.vault = IVault(vault_);
$.tokenName = "Plume USD";
$.tokenSymbol = "pUSD";
$.tokenDecimals = 6;

_grantRole(DEFAULT_ADMIN_ROLE, owner);
_grantRole(VAULT_ADMIN_ROLE, owner);
_grantRole(PAUSER_ROLE, owner);
_grantRole(UPGRADER_ROLE, owner);
}

// ========== ADMIN FUNCTIONS ==========
function setVault(
address newVault
) external onlyRole(VAULT_ADMIN_ROLE) {
address oldVault = address(vault);
vault = IVault(newVault);
pUSDStorage storage $ = _getpUSDStorage();
address oldVault = address($.vault);
$.vault = IVault(newVault);
emit VaultChanged(oldVault, newVault);
}

function pause() external onlyRole(PAUSER_ROLE) {
ungaro marked this conversation as resolved.
Show resolved Hide resolved
paused = true;
_getpUSDStorage().paused = true;
emit Paused(msg.sender);
}

function unpause() external onlyRole(PAUSER_ROLE) {
paused = false;
_getpUSDStorage().paused = false;
emit Unpaused(msg.sender);
}

// Required override for UUPSUpgradeable
function _authorizeUpgrade(
address newImplementation
) internal override onlyRole(UPGRADER_ROLE) { }
// ========== VIEW FUNCTIONS ==========
function vault() external view returns (address) {
return address(_getpUSDStorage().vault);
}

// ========== ERC20 OVERRIDES ==========
function transfer(address to, uint256 amount) public override whenNotPaused returns (bool) {
return vault.transferFrom(msg.sender, to, amount);
// ========== COMPONENT TOKEN INTEGRATION ==========
function deposit(
uint256 assets,
address receiver,
address controller
) public virtual override whenNotPaused returns (uint256 shares) {
shares = super.deposit(assets, receiver, controller);
_getpUSDStorage().vault.enter(address(this), address(asset()), assets, receiver, shares);
return shares;
}

function transferFrom(address from, address to, uint256 amount) public override whenNotPaused returns (bool) {
return vault.transferFrom(from, to, amount);
function redeem(
uint256 shares,
address receiver,
address controller
) public virtual override whenNotPaused returns (uint256 assets) {
assets = super.redeem(shares, receiver, controller);
_getpUSDStorage().vault.exit(receiver, address(asset()), assets, address(this), shares);
return assets;
}
ungaro marked this conversation as resolved.
Show resolved Hide resolved

// ========== ERC20 OVERRIDES ==========
function transfer(
address to,
uint256 amount
) public virtual override(ERC20Upgradeable, IERC20) whenNotPaused returns (bool) {
return _getpUSDStorage().vault.transferFrom(msg.sender, 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 transferFrom(
address from,
address to,
uint256 amount
) public virtual override(ERC20Upgradeable, IERC20) whenNotPaused returns (bool) {
return _getpUSDStorage().vault.transferFrom(from, to, amount);
}

function balanceOf(
address account
) public view override returns (uint256) {
return vault.balanceOf(account);
) public view override(IERC20, ERC20Upgradeable) returns (uint256) {
return _getpUSDStorage().vault.balanceOf(account);
}

// ========== METADATA OVERRIDES ==========

function decimals() public pure override(ERC4626Upgradeable, ERC20Upgradeable, IERC20Metadata) returns (uint8) {
return 6;
}

function name() public pure override(ERC20Upgradeable, IERC20Metadata) returns (string memory) {
return "Plume USD";
}

function symbol() public pure override(ERC20Upgradeable, IERC20Metadata) returns (string memory) {
return "pUSD";
}

// ========== INTERFACE SUPPORT ==========
function supportsInterface(
bytes4 interfaceId
) public view override(AccessControlUpgradeable) returns (bool) {
) public view virtual override(AccessControlUpgradeable, ComponentToken) returns (bool) {
return super.supportsInterface(interfaceId);
}

Expand Down
Loading