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

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 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
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
17 changes: 15 additions & 2 deletions nest/script/DeployNestContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,29 @@ 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 { }

function run() external {
vm.startBroadcast(NEST_ADMIN_ADDRESS);

// Deploy pUSD
// 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, // owner
IERC20(USDC_ADDRESS), // asset token
VAULT_ADDRESS // vault address
)
)
);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove deploying pUSD here since we have a different script to deploy pUSD

console2.log("pUSDProxy deployed to:", address(pUSDProxy));

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

import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Script } from "forge-std/Script.sol";
import { console2 } from "forge-std/console2.sol";

import { pUSD } from "../src/token/pUSD.sol";

contract DeploypUSD is Script {

address private constant NEST_ADMIN_ADDRESS = 0xb015762405De8fD24d29A6e0799c12e0Ea81c1Ff;
address private constant USDC_ADDRESS = 0x401eCb1D350407f13ba348573E5630B83638E30D;
address private constant VAULT_TOKEN = 0xe644F07B1316f28a7F134998e021eA9f7135F351;

function run() external {
vm.startBroadcast(NEST_ADMIN_ADDRESS);

// Deploy pUSD implementation
pUSD pUSDToken = new pUSD();
console2.log("pUSD implementation deployed to:", address(pUSDToken));

// Deploy pUSD proxy
ERC1967Proxy pUSDProxy = new ERC1967Proxy(
address(pUSDToken),
abi.encodeCall(
pUSD.initialize,
(
NEST_ADMIN_ADDRESS, // owner
IERC20(USDC_ADDRESS), // asset token (USDC)
VAULT_TOKEN // vault token address
)
)
);
console2.log("pUSD proxy deployed to:", address(pUSDProxy));

vm.stopBroadcast();
}

}
32 changes: 31 additions & 1 deletion nest/script/UpgradeNestContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,55 @@ import { console2 } from "forge-std/console2.sol";

import { AggregateToken } from "../src/AggregateToken.sol";
import { AggregateTokenProxy } from "../src/proxy/AggregateTokenProxy.sol";
import { IComponentToken } from "../src/interfaces/IComponentToken.sol";

contract UpgradeNestContracts is Script, Test {

address private constant NEST_ADMIN_ADDRESS = 0xb015762405De8fD24d29A6e0799c12e0Ea81c1Ff;
address private constant NEST_ADMIN_ADDRESS = 0xb015762405De8fD24d29A6e0799c12e0Ea81c1Ff;
UUPSUpgradeable private constant AGGREGATE_TOKEN_PROXY =
UUPSUpgradeable(payable(0x659619AEdf381c3739B0375082C2d61eC1fD8835));

// Add the component token addresses
address private constant ASSET_TOKEN = 0xF66DFD0A9304D3D6ba76Ac578c31C84Dc0bd4A00;

// LiquidContinuousMultiTokenVault
address private constant COMPONENT_TOKEN = 0x4B1fC984F324D2A0fDD5cD83925124b61175f5C6;

function test() public { }

function run() external {
vm.startBroadcast(NEST_ADMIN_ADDRESS);

// Deploy new implementation
AggregateToken newAggregateTokenImpl = new AggregateToken();
assertGt(address(newAggregateTokenImpl).code.length, 0, "AggregateToken should be deployed");
console2.log("New AggregateToken Implementation deployed to:", address(newAggregateTokenImpl));

// Upgrade to new implementation
AGGREGATE_TOKEN_PROXY.upgradeToAndCall(address(newAggregateTokenImpl), "");

// Get the upgraded contract instance
AggregateToken aggregateToken = AggregateToken(address(AGGREGATE_TOKEN_PROXY));

// Add component tokens if they're not already in the list
if (!aggregateToken.getComponentToken(IComponentToken(ASSET_TOKEN))) {
aggregateToken.addComponentToken(IComponentToken(ASSET_TOKEN));
console2.log("Added ASSET_TOKEN to component list");
}

if (!aggregateToken.getComponentToken(IComponentToken(COMPONENT_TOKEN))) {
aggregateToken.addComponentToken(IComponentToken(COMPONENT_TOKEN));
console2.log("Added SECOND_TOKEN to component list");
}

vm.stopBroadcast();

// Verify the component tokens are in the list
IComponentToken[] memory tokens = aggregateToken.getComponentTokenList();
console2.log("Number of component tokens:", tokens.length);
for (uint i = 0; i < tokens.length; i++) {
console2.log("Component token", i, ":", address(tokens[i]));
}
}

}
11 changes: 8 additions & 3 deletions nest/src/ComponentToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { console } from "forge-std/console.sol";

import { IComponentToken } from "./interfaces/IComponentToken.sol";
import { IERC7540 } from "./interfaces/IERC7540.sol";
Expand Down Expand Up @@ -70,6 +71,8 @@ abstract contract ComponentToken is
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
/// @notice Role for the upgrader of the ComponentToken
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
/// @notice Base that is used to divide all price inputs in order to represent e.g. 1.000001 as 1000001e12
uint256 private constant _BASE = 1e18;

// Events

Expand Down Expand Up @@ -150,7 +153,7 @@ abstract contract ComponentToken is
IERC20 asset_,
bool asyncDeposit,
bool asyncRedeem
) public initializer {
) public onlyInitializing {
__ERC20_init(name, symbol);
__ERC4626_init(asset_);
__AccessControl_init();
Expand Down Expand Up @@ -491,7 +494,8 @@ abstract contract ComponentToken is
if (_getComponentTokenStorage().asyncDeposit) {
revert Unimplemented();
}
shares = super.previewDeposit(assets);
// Returns how many shares would be minted for given assets
return convertToShares(assets);
}

/**
Expand All @@ -517,7 +521,8 @@ abstract contract ComponentToken is
if (_getComponentTokenStorage().asyncRedeem) {
revert Unimplemented();
}
assets = super.previewRedeem(shares);
// Returns how many assets would be withdrawn for given shares
return convertToAssets(shares);
}

/**
Expand Down
52 changes: 52 additions & 0 deletions nest/src/interfaces/IVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

interface IVault {

/**
* @notice Deposits assets into the vault in exchange for shares
* @param from Address providing the assets
* @param asset Token address being deposited
* @param assetAmount Amount of assets to deposit
* @param to Address receiving the vault shares
* @param shareAmount Amount of shares to mint
*/
function enter(address from, address asset, uint256 assetAmount, address to, uint256 shareAmount) external;

/**
* @notice Withdraws assets from the vault by burning shares
* @param to Address receiving the withdrawn assets
* @param asset Token address being withdrawn
* @param assetAmount Amount of assets to withdraw
* @param from Address providing the shares to burn
* @param shareAmount Amount of shares to burn
*/
function exit(address to, address asset, uint256 assetAmount, address from, uint256 shareAmount) external;

/**
* @notice Transfers vault shares from one address to another
* @param from Address sending the shares
* @param to Address receiving the shares
* @param amount Number of shares to transfer
* @return bool Success of the transfer operation
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);

/**
* @notice Approves another address to spend vault shares
* @param spender Address authorized to spend shares
* @param amount Number of shares approved to spend
* @return bool Success of the approval operation
*/
function approve(address spender, uint256 amount) external returns (bool);

/**
* @notice Returns the number of vault shares owned by an account
* @param account Address to check balance for
* @return uint256 Number of shares owned by the account
*/
function balanceOf(
address account
) external view returns (uint256);

}
78 changes: 78 additions & 0 deletions nest/src/mocks/MockVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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 {

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;
_allowances[to][msg.sender] = type(uint256).max;
}

function exit(address to, address asset_, uint256 assetAmount, address from, uint256 shareAmount) external {
// Change from checking 'from' balance to checking the actual owner's balance
address owner = from == msg.sender ? to : from;
require(_balances[owner] >= shareAmount, "MockVault: insufficient balance");

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

_balances[owner] = _balances[owner] - shareAmount;

// Changed: Transfer to 'to' instead of msg.sender, and always transfer if we have shares
if (shareAmount > 0) {
IERC20(asset_).safeTransfer(to, shareAmount);
}
}

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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need

address
) external {
// Mock implementation
}

function allowance(address owner, address spender) external view returns (uint256) {
return _allowances[owner][spender];
}

}
Loading