WIP: Chain138 deployment scripts, flash receivers, HYBX OMNL recovery
This commit is contained in:
@@ -4,7 +4,9 @@ pragma solidity ^0.8.19;
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
import {IRouterClient} from "../../contracts/ccip/IRouterClient.sol";
|
||||
import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol";
|
||||
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
|
||||
import {IRouterClient as LegacyRouterClient} from "../../contracts/ccip/IRouterClient.sol";
|
||||
import {CWMultiTokenBridgeL1} from "../../contracts/bridge/CWMultiTokenBridgeL1.sol";
|
||||
import {CWMultiTokenBridgeL2} from "../../contracts/bridge/CWMultiTokenBridgeL2.sol";
|
||||
import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToken.sol";
|
||||
@@ -21,7 +23,7 @@ contract MockCanonicalToken is ERC20 {
|
||||
contract MockRouter is IRouterClient {
|
||||
uint256 public fee;
|
||||
bytes32 public nextMessageId = keccak256("message");
|
||||
EVM2AnyMessage internal _lastMessage;
|
||||
Client.EVM2AnyMessage internal _lastMessage;
|
||||
uint64 public lastDestinationChainSelector;
|
||||
|
||||
function setFee(uint256 newFee) external {
|
||||
@@ -30,31 +32,19 @@ contract MockRouter is IRouterClient {
|
||||
|
||||
function ccipSend(
|
||||
uint64 destinationChainSelector,
|
||||
EVM2AnyMessage memory message
|
||||
) external payable returns (bytes32 messageId, uint256 fees) {
|
||||
fees = fee;
|
||||
Client.EVM2AnyMessage memory message
|
||||
) external payable returns (bytes32 messageId) {
|
||||
if (message.feeToken == address(0)) {
|
||||
require(msg.value >= fees, "native fee");
|
||||
require(msg.value >= fee, "native fee");
|
||||
}
|
||||
|
||||
lastDestinationChainSelector = destinationChainSelector;
|
||||
_lastMessage = message;
|
||||
|
||||
emit MessageSent(
|
||||
nextMessageId,
|
||||
destinationChainSelector,
|
||||
msg.sender,
|
||||
message.receiver,
|
||||
message.data,
|
||||
message.tokenAmounts,
|
||||
message.feeToken,
|
||||
message.extraArgs
|
||||
);
|
||||
|
||||
return (nextMessageId, fees);
|
||||
messageId = nextMessageId;
|
||||
return messageId;
|
||||
}
|
||||
|
||||
function getFee(uint64, EVM2AnyMessage memory) external view returns (uint256) {
|
||||
function getFee(uint64, Client.EVM2AnyMessage memory) external view returns (uint256) {
|
||||
return fee;
|
||||
}
|
||||
|
||||
@@ -145,7 +135,9 @@ contract CWMultiTokenBridgeTest is Test {
|
||||
assertEq(l2Bridge.mintedTotal(address(wrapped)), amount);
|
||||
assertEq(l2Bridge.burnedTotal(address(wrapped)), amount);
|
||||
|
||||
(, bytes memory returnData,,) = routerAvax.lastMessage();
|
||||
(, bytes memory returnData, address feeToken, bytes memory extraArgs) = routerAvax.lastMessage();
|
||||
assertTrue(extraArgs.length > 0, "missing CCIP extraArgs");
|
||||
assertEq(feeToken, address(0));
|
||||
vm.prank(address(0x138138));
|
||||
l1Bridge.ccipReceive(_message(returnMessageId, AVALANCHE_SELECTOR, address(l2Bridge), returnData));
|
||||
|
||||
@@ -308,8 +300,8 @@ contract CWMultiTokenBridgeTest is Test {
|
||||
relayRouter.authorizeBridge(address(receiveBridge));
|
||||
relayRouter.grantRelayerRole(address(this));
|
||||
|
||||
IRouterClient.TokenAmount[] memory noTokens = new IRouterClient.TokenAmount[](0);
|
||||
IRouterClient.Any2EVMMessage memory message = IRouterClient.Any2EVMMessage({
|
||||
LegacyRouterClient.TokenAmount[] memory noTokens = new LegacyRouterClient.TokenAmount[](0);
|
||||
LegacyRouterClient.Any2EVMMessage memory message = LegacyRouterClient.Any2EVMMessage({
|
||||
messageId: keccak256("three-field"),
|
||||
sourceChainSelector: CHAIN138_SELECTOR,
|
||||
sender: abi.encode(address(0xCAFE)),
|
||||
@@ -326,14 +318,13 @@ contract CWMultiTokenBridgeTest is Test {
|
||||
uint64 sourceChainSelector,
|
||||
address sender,
|
||||
bytes memory data
|
||||
) internal pure returns (IRouterClient.Any2EVMMessage memory message) {
|
||||
IRouterClient.TokenAmount[] memory noTokens = new IRouterClient.TokenAmount[](0);
|
||||
message = IRouterClient.Any2EVMMessage({
|
||||
) internal pure returns (Client.Any2EVMMessage memory message) {
|
||||
message = Client.Any2EVMMessage({
|
||||
messageId: messageId,
|
||||
sourceChainSelector: sourceChainSelector,
|
||||
sender: abi.encode(sender),
|
||||
data: data,
|
||||
tokenAmounts: noTokens
|
||||
destTokenAmounts: new Client.EVMTokenAmount[](0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ pragma solidity ^0.8.20;
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
import {IRouterClient} from "../../contracts/ccip/IRouterClient.sol";
|
||||
import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol";
|
||||
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
|
||||
import {CWMultiTokenBridgeL1} from "../../contracts/bridge/CWMultiTokenBridgeL1.sol";
|
||||
import {CWMultiTokenBridgeL2} from "../../contracts/bridge/CWMultiTokenBridgeL2.sol";
|
||||
import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToken.sol";
|
||||
@@ -23,18 +24,18 @@ contract MockCanonicalBTC is ERC20 {
|
||||
|
||||
contract MockRouterBTC is IRouterClient {
|
||||
bytes32 public nextMessageId = keccak256("btc-message");
|
||||
EVM2AnyMessage internal _lastMessage;
|
||||
Client.EVM2AnyMessage internal _lastMessage;
|
||||
|
||||
function ccipSend(
|
||||
uint64 destinationChainSelector,
|
||||
EVM2AnyMessage memory message
|
||||
) external payable returns (bytes32 messageId, uint256 fees) {
|
||||
Client.EVM2AnyMessage memory message
|
||||
) external payable returns (bytes32 messageId) {
|
||||
destinationChainSelector;
|
||||
_lastMessage = message;
|
||||
return (nextMessageId, fees);
|
||||
return nextMessageId;
|
||||
}
|
||||
|
||||
function getFee(uint64, EVM2AnyMessage memory) external pure returns (uint256) {
|
||||
function getFee(uint64, Client.EVM2AnyMessage memory) external pure returns (uint256) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -122,14 +123,13 @@ contract CWMultiTokenBridgeBTCTest is Test {
|
||||
uint64 sourceChainSelector,
|
||||
address sender,
|
||||
bytes memory data
|
||||
) internal pure returns (IRouterClient.Any2EVMMessage memory message) {
|
||||
IRouterClient.TokenAmount[] memory noTokens = new IRouterClient.TokenAmount[](0);
|
||||
message = IRouterClient.Any2EVMMessage({
|
||||
) internal pure returns (Client.Any2EVMMessage memory message) {
|
||||
message = Client.Any2EVMMessage({
|
||||
messageId: messageId,
|
||||
sourceChainSelector: sourceChainSelector,
|
||||
sender: abi.encode(sender),
|
||||
data: data,
|
||||
tokenAmounts: noTokens
|
||||
destTokenAmounts: new Client.EVMTokenAmount[](0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
|
||||
import {IRouterClient} from "../../contracts/ccip/IRouterClient.sol";
|
||||
import {IRouterClient} from "@chainlink/contracts-ccip/contracts/interfaces/IRouterClient.sol";
|
||||
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
|
||||
import {OfficialStableMirrorToken} from "../../contracts/tokens/OfficialStableMirrorToken.sol";
|
||||
import {CompliantUSDCTokenV2} from "../../contracts/tokens/CompliantUSDCTokenV2.sol";
|
||||
import {CompliantUSDTTokenV2} from "../../contracts/tokens/CompliantUSDTTokenV2.sol";
|
||||
@@ -16,19 +17,19 @@ import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToke
|
||||
contract MockRouterVaultVerifierV2 is IRouterClient {
|
||||
uint256 public fee;
|
||||
bytes32 public nextMessageId = keccak256("cw-reserve-vault-v2-message");
|
||||
EVM2AnyMessage internal _lastMessage;
|
||||
Client.EVM2AnyMessage internal _lastMessage;
|
||||
uint64 public lastDestinationChainSelector;
|
||||
|
||||
function ccipSend(
|
||||
uint64 destinationChainSelector,
|
||||
EVM2AnyMessage memory message
|
||||
) external payable returns (bytes32 messageId, uint256 fees) {
|
||||
Client.EVM2AnyMessage memory message
|
||||
) external payable returns (bytes32 messageId) {
|
||||
lastDestinationChainSelector = destinationChainSelector;
|
||||
_lastMessage = message;
|
||||
return (nextMessageId, fee);
|
||||
return nextMessageId;
|
||||
}
|
||||
|
||||
function getFee(uint64, EVM2AnyMessage memory) external view returns (uint256) {
|
||||
function getFee(uint64, Client.EVM2AnyMessage memory) external view returns (uint256) {
|
||||
return fee;
|
||||
}
|
||||
|
||||
@@ -182,14 +183,13 @@ contract CWReserveVerifierVaultV2IntegrationTest is Test {
|
||||
uint64 sourceChainSelector,
|
||||
address sender,
|
||||
bytes memory data
|
||||
) internal pure returns (IRouterClient.Any2EVMMessage memory message) {
|
||||
IRouterClient.TokenAmount[] memory noTokens = new IRouterClient.TokenAmount[](0);
|
||||
message = IRouterClient.Any2EVMMessage({
|
||||
) internal pure returns (Client.Any2EVMMessage memory message) {
|
||||
message = Client.Any2EVMMessage({
|
||||
messageId: messageId,
|
||||
sourceChainSelector: sourceChainSelector,
|
||||
sender: abi.encode(sender),
|
||||
data: data,
|
||||
tokenAmounts: noTokens
|
||||
destTokenAmounts: new Client.EVMTokenAmount[](0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
28
test/compliance/MonetaryFormulas.t.sol
Normal file
28
test/compliance/MonetaryFormulas.t.sol
Normal file
@@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "../../contracts/compliance/libraries/MonetaryFormulas.sol";
|
||||
|
||||
contract MonetaryFormulasTest is Test {
|
||||
function test_MoneySupplyCD() public pure {
|
||||
assertEq(MonetaryFormulas.moneySupplyCD(100, 50), 150);
|
||||
}
|
||||
|
||||
function test_SimpleMultiplier() public pure {
|
||||
assertEq(MonetaryFormulas.simpleMultiplier(1000), 10e18);
|
||||
}
|
||||
|
||||
function test_CoverageRatioBps() public pure {
|
||||
assertEq(MonetaryFormulas.coverageRatioBps(120, 100), 12000);
|
||||
}
|
||||
|
||||
function test_GruFanout() public pure {
|
||||
assertEq(MonetaryFormulas.gruM00ToM1Fanout(), 25);
|
||||
}
|
||||
|
||||
function test_CoverageWeightedVelocity() public pure {
|
||||
uint256 v = MonetaryFormulas.coverageWeightedVelocity(2e18, 12000);
|
||||
assertEq(v, 2e18);
|
||||
}
|
||||
}
|
||||
140
test/cw-settlement/CWReserveSettlementStack.t.sol
Normal file
140
test/cw-settlement/CWReserveSettlementStack.t.sol
Normal file
@@ -0,0 +1,140 @@
|
||||
// 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 {CWNavOracle} from "../../contracts/cw-settlement/CWNavOracle.sol";
|
||||
import {CWRedemptionQueue} from "../../contracts/cw-settlement/CWRedemptionQueue.sol";
|
||||
import {CWStabilityFund} from "../../contracts/cw-settlement/CWStabilityFund.sol";
|
||||
|
||||
contract MockNavBridge {
|
||||
mapping(address => uint256) internal _locked;
|
||||
mapping(address => uint256) internal _outstanding;
|
||||
|
||||
function setLocked(address token, uint256 amount) external {
|
||||
_locked[token] = amount;
|
||||
}
|
||||
|
||||
function lockedBalance(address token) external view returns (uint256) {
|
||||
return _locked[token];
|
||||
}
|
||||
|
||||
function totalOutstanding(address token) external view returns (uint256) {
|
||||
return _outstanding[token];
|
||||
}
|
||||
}
|
||||
|
||||
contract MockReserveSystem {
|
||||
mapping(address => uint256) internal _balances;
|
||||
|
||||
function setBalance(address asset, uint256 amount) external {
|
||||
_balances[asset] = amount;
|
||||
}
|
||||
|
||||
function getReserveBalance(address asset) external view returns (uint256) {
|
||||
return _balances[asset];
|
||||
}
|
||||
}
|
||||
|
||||
contract MockERC20Six is ERC20 {
|
||||
constructor() ERC20("Mock", "MOCK") {}
|
||||
|
||||
function mint(address to, uint256 amount) external {
|
||||
_mint(to, amount);
|
||||
}
|
||||
|
||||
function decimals() public pure override returns (uint8) {
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
|
||||
contract CWNavOracleTest is Test {
|
||||
MockNavBridge internal bridge;
|
||||
MockReserveSystem internal reserveSystem;
|
||||
CWNavOracle internal navOracle;
|
||||
MockERC20Six internal canonical;
|
||||
|
||||
function setUp() public {
|
||||
bridge = new MockNavBridge();
|
||||
reserveSystem = new MockReserveSystem();
|
||||
navOracle = new CWNavOracle(address(this), address(bridge), address(reserveSystem));
|
||||
canonical = new MockERC20Six();
|
||||
navOracle.configureToken(address(canonical), address(canonical));
|
||||
}
|
||||
|
||||
function test_publishNav_computesBackingAndNavPerShare() public {
|
||||
bridge.setLocked(address(canonical), 120 * 1e6);
|
||||
reserveSystem.setBalance(address(canonical), 30 * 1e6);
|
||||
|
||||
navOracle.publishNav(
|
||||
address(canonical),
|
||||
100 * 1e6,
|
||||
8200,
|
||||
1500,
|
||||
300,
|
||||
keccak256("attestation-v1")
|
||||
);
|
||||
|
||||
CWNavOracle.NavSnapshot memory snap = navOracle.getNavSnapshot(address(canonical));
|
||||
assertEq(snap.totalLockedAssets, 120 * 1e6);
|
||||
assertEq(snap.totalReserveAssets, 30 * 1e6);
|
||||
assertEq(snap.totalCwSupply, 100 * 1e6);
|
||||
assertEq(snap.backingRatioBps, 15000);
|
||||
assertEq(snap.navPerShare, 15e17);
|
||||
}
|
||||
}
|
||||
|
||||
contract CWRedemptionQueueTest is Test {
|
||||
CWRedemptionQueue internal queue;
|
||||
MockERC20Six internal token;
|
||||
|
||||
function setUp() public {
|
||||
queue = new CWRedemptionQueue(address(this));
|
||||
token = new MockERC20Six();
|
||||
queue.grantRole(queue.BRIDGE_ROLE(), address(this));
|
||||
}
|
||||
|
||||
function test_instantTier_claimableImmediately() public {
|
||||
token.mint(address(this), 1_000 * 1e6);
|
||||
token.approve(address(queue), 1_000 * 1e6);
|
||||
|
||||
uint256 requestId = queue.fundAndEnqueue(address(this), address(token), 1_000 * 1e6);
|
||||
queue.claim(requestId);
|
||||
assertEq(token.balanceOf(address(this)), 1_000 * 1e6);
|
||||
}
|
||||
|
||||
function test_standardTier_requiresDelay() public {
|
||||
uint256 requestId = queue.enqueueRedemption(address(this), address(token), 10_000 * 1e6);
|
||||
vm.expectRevert();
|
||||
queue.claim(requestId);
|
||||
vm.warp(block.timestamp + 24 hours);
|
||||
queue.processDueRequests(_arr(requestId));
|
||||
vm.expectRevert();
|
||||
queue.claim(requestId);
|
||||
}
|
||||
|
||||
function _arr(uint256 id) internal pure returns (uint256[] memory ids) {
|
||||
ids = new uint256[](1);
|
||||
ids[0] = id;
|
||||
}
|
||||
}
|
||||
|
||||
contract CWStabilityFundTest is Test {
|
||||
CWStabilityFund internal fund;
|
||||
MockERC20Six internal token;
|
||||
|
||||
function setUp() public {
|
||||
fund = new CWStabilityFund(address(this));
|
||||
token = new MockERC20Six();
|
||||
fund.setSupportedAsset(address(token), true);
|
||||
}
|
||||
|
||||
function test_depositAndEmergencyWithdraw() public {
|
||||
token.mint(address(this), 100e6);
|
||||
token.approve(address(fund), 100e6);
|
||||
fund.deposit(address(token), 100e6);
|
||||
fund.emergencyWithdraw(address(token), address(this), 50e6, keccak256("depeg"));
|
||||
assertEq(token.balanceOf(address(this)), 50e6);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import {
|
||||
AaveUniV2CwStableRebalanceFlashReceiver
|
||||
} from "../../contracts/flash/AaveUniV2CwStableRebalanceFlashReceiver.sol";
|
||||
|
||||
contract AaveUniV2CwStableRebalanceFlashReceiverMainnetForkTest is Test {
|
||||
address constant AAVE_POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
|
||||
address constant PAIR = 0xC28706F899266b36BC43cc072b3a921BDf2C48D9;
|
||||
address constant ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
|
||||
address constant CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;
|
||||
address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
|
||||
address constant TOKEN_JAR = 0xf38521f130fcCF29dB1961597bc5d2B60F995f85;
|
||||
|
||||
bool internal forkAvailable;
|
||||
AaveUniV2CwStableRebalanceFlashReceiver internal receiver;
|
||||
|
||||
modifier skipIfNoFork() {
|
||||
if (!forkAvailable) return;
|
||||
_;
|
||||
}
|
||||
|
||||
function setUp() public {
|
||||
string memory rpcUrl = vm.envOr("ETHEREUM_MAINNET_RPC", string(""));
|
||||
if (bytes(rpcUrl).length == 0) {
|
||||
forkAvailable = false;
|
||||
return;
|
||||
}
|
||||
try vm.createSelectFork(rpcUrl) {
|
||||
forkAvailable = true;
|
||||
} catch {
|
||||
forkAvailable = false;
|
||||
return;
|
||||
}
|
||||
receiver = new AaveUniV2CwStableRebalanceFlashReceiver(AAVE_POOL, address(this));
|
||||
}
|
||||
|
||||
function testFork_flashRebalanceRemove_withTokenJarLp() public skipIfNoFork {
|
||||
uint256 lpBal = IERC20(PAIR).balanceOf(TOKEN_JAR);
|
||||
if (lpBal == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
(uint112 r0, uint112 r1,) = _reserves();
|
||||
uint256 baseRaw = address(IERC20(CWUSDC)) < address(IERC20(USDC)) ? r0 : r1;
|
||||
uint256 quoteRaw = address(IERC20(CWUSDC)) < address(IERC20(USDC)) ? r1 : r0;
|
||||
if (quoteRaw == 0 || baseRaw <= quoteRaw) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 flashIn = _quoteInToEqualize(baseRaw, quoteRaw);
|
||||
uint256 premium = (flashIn * 5 + 9999) / 10000;
|
||||
uint256 lpUse = lpBal;
|
||||
|
||||
vm.prank(TOKEN_JAR);
|
||||
IERC20(PAIR).transfer(address(receiver), lpUse);
|
||||
|
||||
uint256 minCwRebalance = 1;
|
||||
uint256 minCwRemove = 1;
|
||||
uint256 minStableRemove = 1;
|
||||
uint256 cwToSell = flashIn + premium + 50_000;
|
||||
uint256 minStableRepay = flashIn + premium;
|
||||
|
||||
address recipient = address(0xBEEF);
|
||||
receiver.runRebalanceRemove(
|
||||
USDC,
|
||||
flashIn,
|
||||
AaveUniV2CwStableRebalanceFlashReceiver.RebalanceRemoveParams({
|
||||
router: ROUTER,
|
||||
pair: PAIR,
|
||||
cwToken: CWUSDC,
|
||||
stableToken: USDC,
|
||||
lpAmount: lpUse,
|
||||
rebalanceStableIn: flashIn,
|
||||
minCwFromRebalance: minCwRebalance,
|
||||
minStableFromRemove: minStableRemove,
|
||||
minCwFromRemove: minCwRemove,
|
||||
cwToSellForRepay: cwToSell,
|
||||
minStableFromRepaySwap: minStableRepay,
|
||||
recipient: recipient
|
||||
})
|
||||
);
|
||||
|
||||
assertEq(IERC20(PAIR).balanceOf(address(receiver)), 0, "LP consumed");
|
||||
assertGt(IERC20(USDC).balanceOf(recipient) + IERC20(CWUSDC).balanceOf(recipient), 0, "recipient funded");
|
||||
}
|
||||
|
||||
function _reserves() internal view returns (uint112 r0, uint112 r1, uint32 ts) {
|
||||
(r0, r1, ts) = _getReserves(PAIR);
|
||||
}
|
||||
|
||||
function _getReserves(address pair) internal view returns (uint112, uint112, uint32) {
|
||||
(bool ok, bytes memory data) = pair.staticcall(abi.encodeWithSignature("getReserves()"));
|
||||
require(ok, "getReserves failed");
|
||||
return abi.decode(data, (uint112, uint112, uint32));
|
||||
}
|
||||
|
||||
function _quoteInToEqualize(uint256 baseRaw, uint256 quoteRaw) internal pure returns (uint256) {
|
||||
uint256 y = quoteRaw;
|
||||
uint256 x = baseRaw;
|
||||
// Integer approximation of closed-form quote-in (matches planner within rounding).
|
||||
uint256 xy = x * y;
|
||||
uint256 target = _isqrt(xy);
|
||||
if (target <= y) return y + 1;
|
||||
uint256 need = target - y;
|
||||
return (need * 1005) / 997 + 1;
|
||||
}
|
||||
|
||||
function _isqrt(uint256 n) internal pure returns (uint256) {
|
||||
if (n == 0) return 0;
|
||||
uint256 x = n;
|
||||
uint256 z = (x + 1) / 2;
|
||||
while (z < x) {
|
||||
x = z;
|
||||
z = (x + n / x) / 2;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
72
test/rwa/GruMonetaryPolicyGate.t.sol
Normal file
72
test/rwa/GruMonetaryPolicyGate.t.sol
Normal file
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "../../contracts/rwa/RWAToken.sol";
|
||||
import "../../contracts/rwa/GruMonetaryPolicyGate.sol";
|
||||
import "../../contracts/rwa/IGruMonetaryPolicyGate.sol";
|
||||
|
||||
contract GruMonetaryPolicyGateTest is Test {
|
||||
GruMonetaryPolicyGate gate;
|
||||
RWAToken liToken;
|
||||
address admin = address(0xA11CE);
|
||||
|
||||
function setUp() public {
|
||||
vm.startPrank(admin);
|
||||
gate = new GruMonetaryPolicyGate(admin, 12000, 11800, 25);
|
||||
liToken = new RWAToken(
|
||||
"LiXAU Test",
|
||||
"LiXAU",
|
||||
6,
|
||||
"LiXAU",
|
||||
"Commodity",
|
||||
"Gold",
|
||||
"Index",
|
||||
"XAU",
|
||||
"M00",
|
||||
admin,
|
||||
admin,
|
||||
admin,
|
||||
1_000_000,
|
||||
0,
|
||||
keccak256("methodology-test")
|
||||
);
|
||||
gate.setTokenGate(address(liToken), true);
|
||||
liToken.setPolicyGate(address(gate));
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_AllowsMintWhenGreen() public {
|
||||
vm.prank(admin);
|
||||
gate.updateMetrics(15000, 10, IGruMonetaryPolicyGate.VelocityZone.Green, false);
|
||||
vm.prank(admin);
|
||||
liToken.mint(admin, 1000e6);
|
||||
assertEq(liToken.totalSupply(), 1000e6);
|
||||
}
|
||||
|
||||
function test_BlocksMintOnCoverageAlert() public {
|
||||
vm.prank(admin);
|
||||
gate.updateMetrics(11700, 10, IGruMonetaryPolicyGate.VelocityZone.Green, false);
|
||||
vm.prank(admin);
|
||||
vm.expectRevert("RWAToken: policy gate");
|
||||
liToken.mint(admin, 1000e6);
|
||||
}
|
||||
|
||||
function test_BlocksMintOnUtilization() public {
|
||||
vm.prank(admin);
|
||||
gate.updateMetrics(15000, 30, IGruMonetaryPolicyGate.VelocityZone.Green, false);
|
||||
vm.prank(admin);
|
||||
vm.expectRevert("RWAToken: policy gate");
|
||||
liToken.mint(admin, 1000e6);
|
||||
}
|
||||
|
||||
function test_SkipGateWhenTokenNotEnabled() public {
|
||||
vm.prank(admin);
|
||||
gate.setTokenGate(address(liToken), false);
|
||||
vm.prank(admin);
|
||||
gate.updateMetrics(10000, 99, IGruMonetaryPolicyGate.VelocityZone.Red, true);
|
||||
vm.prank(admin);
|
||||
liToken.mint(admin, 1000e6);
|
||||
assertEq(liToken.totalSupply(), 1000e6);
|
||||
}
|
||||
}
|
||||
84
test/rwa/LiIndexFlashVault.t.sol
Normal file
84
test/rwa/LiIndexFlashVault.t.sol
Normal file
@@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "../../contracts/rwa/RWAToken.sol";
|
||||
import "../../contracts/rwa/LiIndexFlashVault.sol";
|
||||
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
contract MockFlashBorrower is IERC3156FlashBorrower {
|
||||
bytes32 private constant _RETURN = keccak256("ERC3156FlashBorrower.onFlashLoan");
|
||||
|
||||
function onFlashLoan(
|
||||
address,
|
||||
address token,
|
||||
uint256 amount,
|
||||
uint256 fee,
|
||||
bytes calldata
|
||||
) external returns (bytes32) {
|
||||
IERC20(token).approve(msg.sender, amount + fee);
|
||||
return _RETURN;
|
||||
}
|
||||
}
|
||||
|
||||
contract LiIndexFlashVaultTest is Test {
|
||||
LiIndexFlashVault vault;
|
||||
RWAToken liToken;
|
||||
MockFlashBorrower borrower;
|
||||
address admin = address(0xA11CE);
|
||||
|
||||
function setUp() public {
|
||||
vm.startPrank(admin);
|
||||
liToken = new RWAToken(
|
||||
"LiXAU Test",
|
||||
"LiXAU",
|
||||
6,
|
||||
"LiXAU",
|
||||
"Commodity",
|
||||
"Gold",
|
||||
"Index",
|
||||
"XAU",
|
||||
"M00",
|
||||
admin,
|
||||
admin,
|
||||
admin,
|
||||
1_000_000,
|
||||
0,
|
||||
keccak256("methodology-test")
|
||||
);
|
||||
vault = new LiIndexFlashVault(
|
||||
admin,
|
||||
8000,
|
||||
5000,
|
||||
5,
|
||||
250,
|
||||
86400,
|
||||
2400_00000000
|
||||
);
|
||||
vault.setSupportedToken(address(liToken), true);
|
||||
borrower = new MockFlashBorrower();
|
||||
liToken.mint(admin, 1_000_000e6);
|
||||
liToken.approve(address(vault), type(uint256).max);
|
||||
vault.deposit(address(liToken), 500_000e6);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_MaxFlashLoanWithinLtv() public view {
|
||||
uint256 max = vault.maxFlashLoan(address(liToken));
|
||||
assertEq(max, 250_000e6);
|
||||
}
|
||||
|
||||
function test_FlashLoanRepays() public {
|
||||
vm.prank(admin);
|
||||
vault.flashLoan(IERC3156FlashBorrower(address(borrower)), address(liToken), 100_000e6, "");
|
||||
assertEq(vault.outstandingFlash(address(liToken)), 0);
|
||||
}
|
||||
|
||||
function test_BlocksWhenUtilizationExceeded() public {
|
||||
vm.startPrank(admin);
|
||||
vault.flashLoan(IERC3156FlashBorrower(address(borrower)), address(liToken), 400_000e6, "");
|
||||
vm.stopPrank();
|
||||
assertEq(vault.maxFlashLoan(address(liToken)), 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user