change WUSD payment to USDC

This commit is contained in:
2025-12-24 16:41:26 +08:00
parent d2e9377f78
commit e21ee7a5df
160 changed files with 6038 additions and 4050 deletions

View File

@@ -3,6 +3,7 @@ pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../contracts/ytLp/tokens/USDY.sol";
import "../contracts/ytLp/tokens/YTLPToken.sol";
import "../contracts/ytLp/core/YTPriceFeed.sol";
@@ -11,7 +12,80 @@ import "../contracts/ytLp/core/YTPoolManager.sol";
import "../contracts/ytLp/core/YTRewardRouter.sol";
import "../contracts/ytVault/YTAssetVault.sol";
import "../contracts/ytVault/YTAssetFactory.sol";
import "../contracts/ytLp/tokens/WUSD.sol";
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
// Mock USDC token for testing (18 decimals like on BSC)
contract MockUSDC is ERC20 {
constructor() ERC20("USD Coin", "USDC") {
_mint(msg.sender, 100000000 * 1e18);
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function decimals() public pure override returns (uint8) {
return 18; // BSC USDC uses 18 decimals
}
}
// Mock Chainlink Price Feed
contract MockChainlinkPriceFeed is AggregatorV3Interface {
int256 private _price;
uint8 private _decimals;
constructor(int256 initialPrice) {
_price = initialPrice;
_decimals = 8; // Chainlink standard
}
function decimals() external view override returns (uint8) {
return _decimals;
}
function description() external pure override returns (string memory) {
return "Mock USDC/USD Price Feed";
}
function version() external pure override returns (uint256) {
return 1;
}
function getRoundData(uint80)
external
view
override
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return (0, _price, block.timestamp, block.timestamp, 0);
}
function latestRoundData()
external
view
override
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
return (0, _price, block.timestamp, block.timestamp, 0);
}
// Helper function to update price in tests
function updatePrice(int256 newPrice) external {
_price = newPrice;
}
}
contract YTLpTest is Test {
address deployer;
@@ -25,7 +99,8 @@ contract YTLpTest is Test {
YTVault vault;
YTPoolManager poolManager;
YTRewardRouter router;
WUSD wusd;
MockUSDC usdc;
MockChainlinkPriceFeed usdcPriceFeed;
YTAssetFactory factory;
YTAssetVault ytTokenA;
@@ -33,6 +108,8 @@ contract YTLpTest is Test {
YTAssetVault ytTokenC;
uint256 constant PRICE_PRECISION = 1e30;
uint256 constant CHAINLINK_PRECISION = 1e8;
uint256 constant INITIAL_USDC_PRICE = 1e8; // $1.00 in Chainlink format
uint256 constant BASIS_POINTS = 10000;
function setUp() public {
@@ -45,38 +122,35 @@ contract YTLpTest is Test {
vm.deal(user2, 100 ether);
vm.deal(user3, 100 ether);
// ✅ 使用代理模式部署 WUSD
WUSD wusdImpl = new WUSD();
bytes memory wusdInitData = abi.encodeWithSelector(
WUSD.initialize.selector,
"Wrapped USD",
"WUSD"
);
ERC1967Proxy wusdProxy = new ERC1967Proxy(address(wusdImpl), wusdInitData);
wusd = WUSD(address(wusdProxy));
// 部署 Mock USDC (18 decimals)
usdc = new MockUSDC();
// ✅ 使用代理模式部署 USDY
// 部署 Mock Chainlink Price Feed
usdcPriceFeed = new MockChainlinkPriceFeed(int256(INITIAL_USDC_PRICE));
// 使用代理模式部署 USDY
USDY usdyImpl = new USDY();
bytes memory usdyInitData = abi.encodeWithSelector(USDY.initialize.selector);
ERC1967Proxy usdyProxy = new ERC1967Proxy(address(usdyImpl), usdyInitData);
usdy = USDY(address(usdyProxy));
// 使用代理模式部署 YTLPToken
// 使用代理模式部署 YTLPToken
YTLPToken ytlpImpl = new YTLPToken();
bytes memory ytlpInitData = abi.encodeWithSelector(YTLPToken.initialize.selector);
ERC1967Proxy ytlpProxy = new ERC1967Proxy(address(ytlpImpl), ytlpInitData);
ytlp = YTLPToken(address(ytlpProxy));
// 使用代理模式部署 YTPriceFeed
// 使用代理模式部署 YTPriceFeed
YTPriceFeed priceFeedImpl = new YTPriceFeed();
bytes memory priceFeedInitData = abi.encodeWithSelector(
YTPriceFeed.initialize.selector,
address(wusd)
address(usdc),
address(usdcPriceFeed)
);
ERC1967Proxy priceFeedProxy = new ERC1967Proxy(address(priceFeedImpl), priceFeedInitData);
priceFeed = YTPriceFeed(address(priceFeedProxy));
// 使用代理模式部署 YTVault
// 使用代理模式部署 YTVault
YTVault vaultImpl = new YTVault();
bytes memory vaultInitData = abi.encodeWithSelector(
YTVault.initialize.selector,
@@ -86,7 +160,7 @@ contract YTLpTest is Test {
ERC1967Proxy vaultProxy = new ERC1967Proxy(address(vaultImpl), vaultInitData);
vault = YTVault(address(vaultProxy));
// 使用代理模式部署 YTPoolManager
// 使用代理模式部署 YTPoolManager
YTPoolManager poolManagerImpl = new YTPoolManager();
bytes memory poolManagerInitData = abi.encodeWithSelector(
YTPoolManager.initialize.selector,
@@ -98,7 +172,7 @@ contract YTLpTest is Test {
ERC1967Proxy poolManagerProxy = new ERC1967Proxy(address(poolManagerImpl), poolManagerInitData);
poolManager = YTPoolManager(address(poolManagerProxy));
// 使用代理模式部署 YTRewardRouter
// 使用代理模式部署 YTRewardRouter
YTRewardRouter routerImpl = new YTRewardRouter();
bytes memory routerInitData = abi.encodeWithSelector(
YTRewardRouter.initialize.selector,
@@ -110,10 +184,10 @@ contract YTLpTest is Test {
ERC1967Proxy routerProxy = new ERC1967Proxy(address(routerImpl), routerInitData);
router = YTRewardRouter(address(routerProxy));
// 部署YTAssetVault实现合约不初始化
// 部署YTAssetVault实现合约不初始化
YTAssetVault ytVaultImpl = new YTAssetVault();
// 使用代理模式部署 YTAssetFactory
// 使用代理模式部署 YTAssetFactory
YTAssetFactory factoryImpl = new YTAssetFactory();
bytes memory factoryInitData = abi.encodeWithSelector(
YTAssetFactory.initialize.selector,
@@ -129,10 +203,10 @@ contract YTLpTest is Test {
"YT-A",
deployer, // manager
1000000 ether, // hardCap
address(wusd),
address(usdc),
block.timestamp + 365 days, // redemptionTime
PRICE_PRECISION, // wusdPrice = 1.0
PRICE_PRECISION // ytPrice = 1.0
PRICE_PRECISION, // initialYtPrice
address(usdcPriceFeed) // usdcPriceFeed
);
ytTokenA = YTAssetVault(ytTokenAAddr);
@@ -141,10 +215,10 @@ contract YTLpTest is Test {
"YT-B",
deployer,
1000000 ether,
address(wusd),
address(usdc),
block.timestamp + 365 days,
PRICE_PRECISION,
PRICE_PRECISION
address(usdcPriceFeed)
);
ytTokenB = YTAssetVault(ytTokenBAddr);
@@ -153,10 +227,10 @@ contract YTLpTest is Test {
"YT-C",
deployer,
1000000 ether,
address(wusd),
address(usdc),
block.timestamp + 365 days,
PRICE_PRECISION,
PRICE_PRECISION
address(usdcPriceFeed)
);
ytTokenC = YTAssetVault(ytTokenCAddr);
@@ -174,9 +248,6 @@ contract YTLpTest is Test {
vault.setMaxSwapSlippageBps(1000);
priceFeed.setMaxPriceChangeBps(500);
// 设置WUSD价格来源使用ytTokenA
priceFeed.setWusdPriceSource(address(ytTokenA));
// 配置白名单
vault.setWhitelistedToken(address(ytTokenA), 18, 4000, 45000000 ether, false);
vault.setWhitelistedToken(address(ytTokenB), 18, 3000, 35000000 ether, false);
@@ -189,20 +260,20 @@ contract YTLpTest is Test {
// 不设置价差(便于精确计算)
// 为测试用户铸造YT代币需要先给合约WUSD再depositYT
// 为测试用户铸造YT代币需要先给合约USDC再depositYT
uint256 initialAmount = 10000 ether;
// 给deployer一些WUSD用于购买YT
wusd.mint(deployer, 30000 ether);
// 给deployer一些USDC用于购买YT
usdc.mint(deployer, 30000 ether);
// Deployer购买YT代币
wusd.approve(address(ytTokenA), initialAmount);
usdc.approve(address(ytTokenA), initialAmount);
ytTokenA.depositYT(initialAmount);
wusd.approve(address(ytTokenB), initialAmount);
usdc.approve(address(ytTokenB), initialAmount);
ytTokenB.depositYT(initialAmount);
wusd.approve(address(ytTokenC), initialAmount);
usdc.approve(address(ytTokenC), initialAmount);
ytTokenC.depositYT(initialAmount);
// 转账给用户
@@ -213,10 +284,10 @@ contract YTLpTest is Test {
ytTokenA.transfer(user2, 3000 ether);
ytTokenB.transfer(user2, 3000 ether);
// 给用户一些WUSD用于后续可能的操作
wusd.mint(user1, 10000 ether);
wusd.mint(user2, 10000 ether);
wusd.mint(user3, 10000 ether);
// 给用户一些USDC(用于后续可能的操作)
usdc.mint(user1, 10000 ether);
usdc.mint(user2, 10000 ether);
usdc.mint(user3, 10000 ether);
}
// ==================== 1. 部署和初始化测试 ====================
@@ -268,7 +339,6 @@ contract YTLpTest is Test {
assertEq(ytTokenA.name(), "YT Token A");
assertEq(ytTokenA.symbol(), "YT-A");
assertEq(ytTokenA.ytPrice(), PRICE_PRECISION);
assertEq(ytTokenA.wusdPrice(), PRICE_PRECISION);
}
// ==================== 2. 添加流动性测试(精确计算)====================
@@ -389,8 +459,7 @@ contract YTLpTest is Test {
);
vm.stopPrank();
uint256 expectedOut = 997 ether;
uint256 expectedOut = 997 ether; // 这里能够取出997是因为池子中只有一个用户user1的997个ytLp代币价值1000USDY卖出后获得997个YT-A代币
assertEq(amountOut, expectedOut, "remove liquidity amount incorrect");
assertEq(ytTokenA.balanceOf(user1), tokenBalanceBefore + expectedOut, "user1 final balance incorrect");
assertEq(ytlp.balanceOf(user1), 0, "ytLP should be burned");
@@ -498,45 +567,44 @@ contract YTLpTest is Test {
priceFeed.setSpreadBasisPoints(address(ytTokenA), 0);
}
function test_15_WUSDPriceFromVault() public view {
// WUSD价格应该从ytTokenA读取
uint256 wusdPrice = priceFeed.getPrice(address(wusd), true);
function test_15_USDCPriceFromChainlink() public view {
// USDC价格应该从Chainlink读取
uint256 usdcPrice = priceFeed.getPrice(address(usdc), true);
// 应该等于ytTokenA的wusdPrice
assertEq(wusdPrice, PRICE_PRECISION, "WUSD price should be 1.0");
// 应该等于 $1.00 (转换为 1e30 精度)
// Chainlink 返回 1e8需要转换为 1e30: 1e8 * 1e22 = 1e30
assertEq(usdcPrice, PRICE_PRECISION, "USDC price should be 1.0");
}
// ==================== 6. YTAssetVault特定功能测试 ====================
function test_16_UpdateYTPrices() public {
uint256 newWusdPrice = 1.01e30; // $1.01
uint256 newYtPrice = 1.05e30; // $1.05
// 通过factory更新价格
factory.updateVaultPrices(address(ytTokenA), newWusdPrice, newYtPrice);
// 通过factory更新价格USDC价格从Chainlink获取只更新ytPrice
factory.updateVaultPrices(address(ytTokenA), newYtPrice);
assertEq(ytTokenA.wusdPrice(), newWusdPrice, "wusdPrice update failed");
assertEq(ytTokenA.ytPrice(), newYtPrice, "ytPrice update failed");
// 重置价格
factory.updateVaultPrices(address(ytTokenA), PRICE_PRECISION, PRICE_PRECISION);
factory.updateVaultPrices(address(ytTokenA), PRICE_PRECISION);
}
function test_17_BuyYTWithWUSD() public {
uint256 wusdAmount = 1000 ether;
function test_17_BuyYTWithUSDC() public {
uint256 usdcAmount = 1000 ether;
vm.startPrank(user1);
wusd.approve(address(ytTokenA), wusdAmount);
usdc.approve(address(ytTokenA), usdcAmount);
uint256 ytBefore = ytTokenA.balanceOf(user1);
uint256 ytReceived = ytTokenA.depositYT(wusdAmount);
uint256 ytReceived = ytTokenA.depositYT(usdcAmount);
uint256 ytAfter = ytTokenA.balanceOf(user1);
vm.stopPrank();
// 价格都是1.0应该1:1兑换
assertEq(ytReceived, wusdAmount, "YT amount should equal WUSD amount");
assertEq(ytAfter - ytBefore, wusdAmount, "YT balance incorrect");
assertEq(ytReceived, usdcAmount, "YT amount should equal USDC amount");
assertEq(ytAfter - ytBefore, usdcAmount, "YT balance incorrect");
}
function test_18_HardCapProtection() public {
@@ -548,7 +616,7 @@ contract YTLpTest is Test {
factory.setHardCap(address(ytTokenA), newHardCap);
vm.startPrank(user1);
wusd.approve(address(ytTokenA), 1000 ether);
usdc.approve(address(ytTokenA), 1000 ether);
// 尝试存入1000 ether会超过hardCap应该revert
vm.expectRevert(abi.encodeWithSignature("HardCapExceeded()"));
@@ -566,7 +634,7 @@ contract YTLpTest is Test {
vm.startPrank(user1);
vm.expectRevert(abi.encodeWithSignature("Forbidden()"));
ytTokenA.updatePrices(1e30, 1e30);
ytTokenA.updatePrices(1e30);
vm.stopPrank();
}
@@ -620,8 +688,6 @@ contract YTLpTest is Test {
uint256 tokenOut = router.removeLiquidity(address(ytTokenA), removeAmount, 0, user1);
console.log("User1 removed half ytLP, received YT-A:", tokenOut);
vm.stopPrank();
assertTrue(tokenOut > 990 ether && tokenOut < 1000 ether, "token out should be around 997");
}
// ==================== 9. 手续费测试 ====================
@@ -694,16 +760,16 @@ contract YTLpTest is Test {
"YT-D",
deployer,
1000000 ether,
address(wusd),
address(usdc),
block.timestamp + 365 days,
PRICE_PRECISION,
PRICE_PRECISION
address(usdcPriceFeed)
);
YTAssetVault ytTokenD = YTAssetVault(ytTokenDAddr);
// 铸造一些YT-D
wusd.mint(deployer, 1000 ether);
wusd.approve(address(ytTokenD), 1000 ether);
usdc.mint(deployer, 1000 ether);
usdc.approve(address(ytTokenD), 1000 ether);
ytTokenD.depositYT(1000 ether);
// 添加到白名单
@@ -1097,17 +1163,17 @@ contract YTLpTest is Test {
"YT-D",
deployer,
1000000 ether,
address(wusd),
address(usdc),
block.timestamp + 365 days,
PRICE_PRECISION,
PRICE_PRECISION
address(usdcPriceFeed)
);
YTAssetVault ytTokenD = YTAssetVault(ytTokenDAddr);
wusd.mint(user1, 500 ether);
usdc.mint(user1, 500 ether);
vm.startPrank(user1);
wusd.approve(address(ytTokenD), 500 ether);
usdc.approve(address(ytTokenD), 500 ether);
ytTokenD.depositYT(500 ether);
// 添加YT-A流动性
@@ -1165,9 +1231,10 @@ contract YTLpTest is Test {
uint256 poolAmountAfter = vault.poolAmounts(address(ytTokenA));
uint256 feeInPool = poolAmountBefore - poolAmountAfter;
assertEq(feeInPool, 997 ether, "fee should be collected");
uint256 amountRemoved = poolAmountBefore - poolAmountAfter;
assertEq(amountRemoved, 997 ether, "amount removed from pool incorrect");
// 池子剩余:添加时的手续费 3 ether
assertEq(poolAmountAfter, 3 ether, "remaining pool incorrect");
}
@@ -1634,5 +1701,4 @@ contract YTLpTest is Test {
}
receive() external payable {}
}
}