change WUSD payment to USDC
This commit is contained in:
216
test/YTLp.t.sol
216
test/YTLp.t.sol
@@ -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 {}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user