- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault. - Token-aggregation service routes, planner, chain config, relay env templates. - Config snapshots and multi-chain deployment markdown updates. - gitignore services/btc-intake/dist/ (tsc output); do not track dist. Run forge build && forge test before deploy (large solc graph). Made-with: Cursor
125 lines
4.3 KiB
Solidity
125 lines
4.3 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {Test} from "forge-std/Test.sol";
|
|
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
|
|
import {USDWPublicWrapVault} from "../../contracts/bridge/integration/USDWPublicWrapVault.sol";
|
|
import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToken.sol";
|
|
|
|
contract MockNativeUSDW is ERC20 {
|
|
uint8 private immutable _decimalsValue;
|
|
|
|
constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {
|
|
_decimalsValue = decimals_;
|
|
}
|
|
|
|
function decimals() public view override returns (uint8) {
|
|
return _decimalsValue;
|
|
}
|
|
|
|
function mint(address to, uint256 amount) external {
|
|
_mint(to, amount);
|
|
}
|
|
}
|
|
|
|
contract USDWPublicWrapVaultTest is Test {
|
|
address internal admin = address(0xA11CE);
|
|
address internal operator = address(0xB0B);
|
|
address internal user = address(0xCAFE);
|
|
address internal bridgeUser = address(0xD00D);
|
|
|
|
MockNativeUSDW internal nativeUsdw;
|
|
MockNativeUSDW internal strayToken;
|
|
CompliantWrappedToken internal wrappedUsdw;
|
|
USDWPublicWrapVault internal vault;
|
|
|
|
function setUp() public {
|
|
vm.startPrank(admin);
|
|
nativeUsdw = new MockNativeUSDW("USD DWIN", "USDW", 18);
|
|
strayToken = new MockNativeUSDW("Stray", "STRAY", 18);
|
|
wrappedUsdw = new CompliantWrappedToken("Wrapped cUSDW", "cWUSDW", 6, admin);
|
|
vault = new USDWPublicWrapVault(admin, address(nativeUsdw), address(wrappedUsdw));
|
|
vault.grantRole(vault.RESERVE_OPERATOR_ROLE(), operator);
|
|
wrappedUsdw.grantRole(wrappedUsdw.MINTER_ROLE(), address(vault));
|
|
wrappedUsdw.grantRole(wrappedUsdw.BURNER_ROLE(), address(vault));
|
|
vm.stopPrank();
|
|
|
|
nativeUsdw.mint(user, 100e18);
|
|
nativeUsdw.mint(operator, 100e18);
|
|
strayToken.mint(address(vault), 5e18);
|
|
}
|
|
|
|
function testWrapNormalizesNativeAmountToWrappedDecimals() public {
|
|
vm.startPrank(user);
|
|
nativeUsdw.approve(address(vault), 5e18);
|
|
uint256 wrappedAmount = vault.wrap(5e18, user);
|
|
vm.stopPrank();
|
|
|
|
assertEq(wrappedAmount, 5e6);
|
|
assertEq(wrappedUsdw.balanceOf(user), 5e6);
|
|
assertEq(nativeUsdw.balanceOf(address(vault)), 5e18);
|
|
assertEq(nativeUsdw.balanceOf(user), 95e18);
|
|
}
|
|
|
|
function testUnwrapBurnsWrappedAndReleasesNativeUsd() public {
|
|
vm.startPrank(user);
|
|
nativeUsdw.approve(address(vault), 7e18);
|
|
vault.wrap(7e18, user);
|
|
uint256 unwrapped = vault.unwrap(2e6, user);
|
|
vm.stopPrank();
|
|
|
|
assertEq(unwrapped, 2e18);
|
|
assertEq(wrappedUsdw.balanceOf(user), 5e6);
|
|
assertEq(nativeUsdw.balanceOf(user), 95e18);
|
|
assertEq(nativeUsdw.balanceOf(address(vault)), 5e18);
|
|
}
|
|
|
|
function testBridgeMintedSupplyCanUnwrapAgainstSeededLiquidity() public {
|
|
vm.prank(operator);
|
|
nativeUsdw.approve(address(vault), 20e18);
|
|
vm.prank(operator);
|
|
vault.seedLiquidity(20e18);
|
|
|
|
vm.prank(admin);
|
|
wrappedUsdw.mint(bridgeUser, 3e6);
|
|
|
|
vm.prank(bridgeUser);
|
|
uint256 released = vault.unwrap(3e6, bridgeUser);
|
|
|
|
assertEq(released, 3e18);
|
|
assertEq(nativeUsdw.balanceOf(bridgeUser), 3e18);
|
|
assertEq(nativeUsdw.balanceOf(address(vault)), 17e18);
|
|
assertEq(wrappedUsdw.balanceOf(bridgeUser), 0);
|
|
}
|
|
|
|
function testWrapRejectsNonCanonicalNativeAmount() public {
|
|
vm.startPrank(user);
|
|
nativeUsdw.approve(address(vault), 1e18 + 1);
|
|
vm.expectRevert(abi.encodeWithSelector(USDWPublicWrapVault.NonCanonicalAmount.selector, 1e18 + 1));
|
|
vault.wrap(1e18 + 1, user);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testPauseBlocksWrapAndUnwrap() public {
|
|
vm.prank(admin);
|
|
vault.pause();
|
|
|
|
vm.startPrank(user);
|
|
nativeUsdw.approve(address(vault), 1e18);
|
|
vm.expectRevert();
|
|
vault.wrap(1e18, user);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testRecoverNonUnderlyingTokenButProtectNativeReserve() public {
|
|
vm.prank(admin);
|
|
vault.recoverNonUnderlyingToken(address(strayToken), admin, 5e18);
|
|
assertEq(strayToken.balanceOf(admin), 5e18);
|
|
|
|
vm.prank(admin);
|
|
vm.expectRevert(USDWPublicWrapVault.UnderlyingTokenProtected.selector);
|
|
vault.recoverNonUnderlyingToken(address(nativeUsdw), admin, 1);
|
|
}
|
|
}
|