Skip to content

Commit

Permalink
test: lz test and improvements to strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
CarsonCase committed Oct 1, 2024
1 parent 52b612d commit 0ecc737
Show file tree
Hide file tree
Showing 9 changed files with 4,574 additions and 156 deletions.
4,405 changes: 4,288 additions & 117 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
"@layerzerolabs/lz-evm-protocol-v2": "^2.3.25",
"@layerzerolabs/lz-evm-v1-0.7": "^2.3.25",
"@layerzerolabs/oft-evm": "^0.0.11",
"@stargatefinance/stg-evm-v2": "^1.0.16",
"forge-std": "^1.1.2",
"i": "^0.3.7",
"npm": "^10.8.3",
"prettier": "^3.3.2",
"solhint": "^5.0.1"
}
Expand Down
16 changes: 10 additions & 6 deletions src/base/DecodersAndSanitizers/LayerZeroOFTDecoderAndSanitizer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { MessagingFee } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";

contract LayerZeroOFTDecoderAndSanitizer is BaseDecoderAndSanitizer {
error LayerZeroOFTDecoderAndSanitizer_ComposedMsgNotSupported();
error LayerZeroOFTDecoderAndSanitizer_OftCmdNotSupported();

constructor(address _boringVault) BaseDecoderAndSanitizer(_boringVault) { }

Expand All @@ -24,14 +23,19 @@ contract LayerZeroOFTDecoderAndSanitizer is BaseDecoderAndSanitizer {
* uint256 nativeFee;
* uint256 lzTokenFee;
*/
function send(SendParam calldata _sendParam, MessagingFee calldata, address) external pure returns (bytes memory) {
function send(
SendParam calldata _sendParam,
MessagingFee calldata,
address refundReceiver
)
external
pure
returns (bytes memory)
{
if (_sendParam.composeMsg.length > 0) {
revert LayerZeroOFTDecoderAndSanitizer_ComposedMsgNotSupported();
}
if (_sendParam.oftCmd.length > 0) {
revert LayerZeroOFTDecoderAndSanitizer_OftCmdNotSupported();
}

return abi.encodePacked(_sendParam.dstEid, _sendParam.to);
return abi.encodePacked(_sendParam.dstEid, _sendParam.to, refundReceiver);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import { BaseDecoderAndSanitizer } from "src/base/DecodersAndSanitizers/BaseDecoderAndSanitizer.sol";

abstract contract PirexEthDecoderAndSanitizer is BaseDecoderAndSanitizer {
function deposit(address receiver, bool) external returns (bytes memory) {
return abi.encodePacked(receiver);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import { BaseDecoderAndSanitizer } from "src/base/DecodersAndSanitizers/BaseDecoderAndSanitizer.sol";
import { ERC4626DecoderAndSanitizer } from "src/base/DecodersAndSanitizers/Protocols/ERC4626DecoderAndSanitizer.sol";
import { PirexEthDecoderAndSanitizer } from "src/base/DecodersAndSanitizers/Protocols/PirexEthDecoderAndSanitizer.sol";
import { NativeWrapperDecoderAndSanitizer } from
"src/base/DecodersAndSanitizers/Protocols/NativeWrapperDecoderAndSanitizer.sol";

contract SeiyanEthRebalanceDecoderAndSanitizer is
BaseDecoderAndSanitizer,
ERC4626DecoderAndSanitizer,
PirexEthDecoderAndSanitizer,
NativeWrapperDecoderAndSanitizer
{
constructor(address _boringVault) BaseDecoderAndSanitizer(_boringVault) { }
}
38 changes: 38 additions & 0 deletions src/base/DecodersAndSanitizers/StargateV2DecoderAndSanitizer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import { BaseDecoderAndSanitizer } from "src/base/DecodersAndSanitizers/BaseDecoderAndSanitizer.sol";
import { MessagingFee, OFTReceipt, SendParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol";

contract StargateV2DecoderAndSanitizer is BaseDecoderAndSanitizer {
constructor(address _boringVault) BaseDecoderAndSanitizer(_boringVault) { }

error StargateV2DecoderAndSanitizer_ComposedMsgNotSupported();

/**
* @dev _sendParam:
* uint32 dstEid; // Destination endpoint ID. [VERIFY]
* bytes32 to; // Recipient address. [VERIFY]
* uint256 amountLD; // Amount to send in local decimals.
* uint256 minAmountLD; // Minimum amount to send in local decimals.
* bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.
* bytes composeMsg; // The composed message for the send() operation. [NONE]
* bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations. [NONE]
* @dev _fee:
* uint256 nativeFee;
* uint256 lzTokenFee;
*/
function sendToken(
SendParam memory _sendParam,
MessagingFee memory _messagingFee,
address _refundAddress
)
external
returns (bytes memory)
{
if (_sendParam.composeMsg.length > 0) {
revert StargateV2DecoderAndSanitizer_ComposedMsgNotSupported();
}
return abi.encodePacked(_sendParam.dstEid, _sendParam.to, _refundAddress);
}
}
105 changes: 79 additions & 26 deletions test/strategy/LayerZeroOFTStrategy.t.sol
Original file line number Diff line number Diff line change
@@ -1,57 +1,110 @@
pragma solidity 0.8.21;

import { StrategyBase, Leaf } from "./StrategyBase.t.sol";
import { LayerZeroOFTDecoderAndSanitizer } from "src/base/DecodersAndSanitizers/LayerZeroOFTDecoderAndSanitizer.sol";
import { LiveSetup } from "../LiveSetup.t.sol";

import {
LayerZeroOFTDecoderAndSanitizer,
BaseDecoderAndSanitizer
} from "src/base/DecodersAndSanitizers/LayerZeroOFTDecoderAndSanitizer.sol";
import { BoringVault } from "src/base/BoringVault.sol";
import { IStargate } from "@stargatefinance/stg-evm-v2/src/interfaces/IStargate.sol";
import { ManagerWithMerkleVerification } from "src/base/Roles/ManagerWithMerkleVerification.sol";
import { MessagingFee, OFTReceipt, SendParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol";
import { ERC20 } from "@solmate/tokens/ERC20.sol";
import { console } from "@forge-std/Test.sol";
import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";

address constant ADMIN = 0xF2dE1311C5b2C1BD94de996DA13F80010453e505;
address constant WETH = 0x160345fC359604fC6e70E3c5fAcbdE5F7A9342d8;
address constant STARGATE = 0x5c386D85b1B82FD9Db681b9176C8a4248bb6345B;
uint32 constant ETH_EID = 30_101;
uint256 constant SEI_TO_MINT = 100 ether;

contract LayerZeroOFTStrategy is StrategyBase, LiveSetup {
uint256 constant ETH_EID = 30_101;
contract LayerZeroOFTStrategy is StrategyBase {
using OptionsBuilder for bytes;

LayerZeroOFTDecoderAndSanitizer sanitizer;

function setUp() public override(StrategyBase, LiveSetup) {
LiveSetup.setUp();
StrategyBase.setUp();
ManagerWithMerkleVerification manager = ManagerWithMerkleVerification(0x9B99d4584a3858C639F94fE055DB9E94017fE009);
BoringVault boringVault = BoringVault(payable(0x9fAaEA2CDd810b21594E54309DC847842Ae301Ce));

function setUp() public override {
uint256 forkId = vm.createFork(vm.envString("L2_RPC_URL"));
vm.selectFork(forkId);
super.setUp();
}

function setUpDecoderSanitizers() public override {
sanitizer = new LayerZeroOFTDecoderAndSanitizer(mainConfig.boringVault);
sanitizer = new LayerZeroOFTDecoderAndSanitizer(address(boringVault));
}

function testSend() external {
deal(address(boringVault), SEI_TO_MINT);
uint256 amountToSend = ERC20(WETH).balanceOf(address(boringVault));
console.log(amountToSend);
// owner prank
// deploy sanitizer and build tree
vm.startPrank(mainConfig.protocolAdmin);
bytes memory packedArguments = abi.encodePacked(ETH_EID, address(this));
Leaf memory myLeaf = Leaf(
address(sanitizer), mainConfig.base, false, LayerZeroOFTDecoderAndSanitizer.send.selector, packedArguments
);
buildExampleTree(myLeaf);
vm.startPrank(ADMIN);
bytes memory packedArguments = abi.encodePacked(STARGATE);
Leaf memory approveLeaf =
Leaf(address(sanitizer), WETH, false, BaseDecoderAndSanitizer.approve.selector, packedArguments);
packedArguments = abi.encodePacked(ETH_EID, addressToBytes32(address(boringVault)), ADMIN);
Leaf memory sendLeaf =
Leaf(address(sanitizer), STARGATE, true, LayerZeroOFTDecoderAndSanitizer.send.selector, packedArguments);

Leaf[] memory myLeafs = new Leaf[](2);
myLeafs[0] = approveLeaf;
myLeafs[1] = sendLeaf;
buildExampleTree(myLeafs);

// set leaf in manager
ManagerWithMerkleVerification manager = ManagerWithMerkleVerification(mainConfig.manager);
manager.setManageRoot(mainConfig.strategist, _getRoot());
ManagerWithMerkleVerification manager = ManagerWithMerkleVerification(manager);
manager.setManageRoot(ADMIN, _getRoot());
vm.stopPrank();

// strategist manages vault
vm.startPrank(mainConfig.strategist);
bytes32[][] memory manageProofs = new bytes32[][](1);
manageProofs[0] = _generateProof(_hashLeaf(myLeaf), tree);
// in this case strategist is also admin
vm.startPrank(ADMIN);
bytes32[][] memory manageProofs = new bytes32[][](2);
manageProofs[0] = _generateProof(_hashLeaf(approveLeaf), tree);
manageProofs[1] = _generateProof(_hashLeaf(sendLeaf), tree);

address[] memory decodersAndSanitizers = new address[](1);
address[] memory decodersAndSanitizers = new address[](2);
decodersAndSanitizers[0] = address(sanitizer);
decodersAndSanitizers[1] = address(sanitizer);

address[] memory targets = new address[](2);
targets[0] = WETH;
targets[1] = STARGATE;

address[] memory targets = new address[](1);
targets[0] = mainConfig.base;
bytes[] memory targetData = new bytes[](2);
targetData[0] = abi.encodeWithSelector(BaseDecoderAndSanitizer.approve.selector, STARGATE, amountToSend);

bytes[] memory targetData = new bytes[](1);
targetData[0] = abi.encodeWithSelector(LayerZeroOFTDecoderAndSanitizer.send.selector, packedArguments);
bytes memory _extraOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(80_000, 0);
SendParam memory sp = SendParam(
ETH_EID, addressToBytes32(address(boringVault)), amountToSend, amountToSend, _extraOptions, "", new bytes(0)
);

(,, OFTReceipt memory receipt) = IStargate(STARGATE).quoteOFT(sp);
sp.minAmountLD = receipt.amountReceivedLD;

MessagingFee memory mf = IStargate(STARGATE).quoteSend(sp, false);
uint256 valueToSend = mf.nativeFee;

uint256[] memory values = new uint256[](1);
targetData[1] = abi.encodeWithSelector(LayerZeroOFTDecoderAndSanitizer.send.selector, sp, mf, ADMIN);

uint256[] memory values = new uint256[](2);
values[0] = 0;
values[1] = valueToSend;

manager.manageVaultWithMerkleVerification(manageProofs, decodersAndSanitizers, targets, targetData, values);
console.log("MinAmountLD: ", sp.minAmountLD);
console.log("AmountLD: ", sp.amountLD);
console.log("SEI Spent: ", SEI_TO_MINT - address(boringVault).balance);
console.log("Value to Send: ", valueToSend);
assertEq(ERC20(WETH).balanceOf(address(boringVault)), 0, "Boring Vault should have no more WETH");
}

function addressToBytes32(address _addr) public pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
}
118 changes: 118 additions & 0 deletions test/strategy/SeiyanEthRebalance.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
pragma solidity 0.8.21;

import { StrategyBase, Leaf } from "./StrategyBase.t.sol";
import { SeiyanEthRebalanceDecoderAndSanitizer } from
"src/base/DecodersAndSanitizers/SeiyanEthRebalanceDecoderAndSanitizer.sol";
import { NativeWrapperDecoderAndSanitizer } from
"src/base/DecodersAndSanitizers/Protocols/NativeWrapperDecoderAndSanitizer.sol";
import { PirexEthDecoderAndSanitizer } from "src/base/DecodersAndSanitizers/Protocols/PirexEthDecoderAndSanitizer.sol";
import { ManagerWithMerkleVerification } from "src/base/Roles/ManagerWithMerkleVerification.sol";
import { BoringVault } from "src/base/BoringVault.sol";
import { ERC20 } from "@solmate/tokens/ERC20.sol";
import { console } from "@forge-std/Test.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";

address constant ADMIN = 0x0000000000417626Ef34D62C4DC189b021603f2F;
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address constant APX_ETH = 0x9Ba021B0a9b958B5E75cE9f6dff97C7eE52cb3E6;
address constant PIREX_ETH = 0xD664b74274DfEB538d9baC494F3a4760828B02b0;
uint256 constant SEI_WETH = 93.5214859e18;
uint256 constant TARGET_APX_ETH = 235.2211814e18;

contract SeiyanEthRebalanceStrategyTest is StrategyBase {
using Address for address;

ERC20 base = ERC20(WETH);
ManagerWithMerkleVerification manager = ManagerWithMerkleVerification(0x9B99d4584a3858C639F94fE055DB9E94017fE009);
BoringVault boringVault = BoringVault(payable(0x9fAaEA2CDd810b21594E54309DC847842Ae301Ce));
SeiyanEthRebalanceDecoderAndSanitizer decoder;

function setUp() public override {
uint256 forkId = vm.createFork(vm.envString("L1_RPC_URL"));
vm.selectFork(forkId);
super.setUp();
}

function setUpDecoderSanitizers() public override {
decoder = new SeiyanEthRebalanceDecoderAndSanitizer(address(boringVault));
}

function testRebalance() public {
// to-do DEAL new WETH that would be bridged
uint256 vaultBalance = base.balanceOf(address(boringVault));
deal(WETH, address(boringVault), vaultBalance + SEI_WETH);
vaultBalance = base.balanceOf(address(boringVault));
_setUpManager(vaultBalance);
assertEq(manager.manageRoot(ADMIN), _getRoot(), "Root not set correctly");

Leaf[] memory myLeafs = _getLeafsForTest(vaultBalance);

bytes32[][] memory manageProofs = new bytes32[][](2);
address[] memory decodersAndSanitizers = new address[](2);
address[] memory targets = new address[](2);
bytes[] memory targetData = new bytes[](2);
uint256[] memory values = new uint256[](2);

manageProofs = _getProofsUsingTree(myLeafs, tree);
decodersAndSanitizers[0] = myLeafs[0].decoderAndSanitizer;
decodersAndSanitizers[1] = myLeafs[1].decoderAndSanitizer;

targets[0] = myLeafs[0].target;
targets[1] = myLeafs[1].target;

targetData[0] = abi.encodeWithSelector(myLeafs[0].selector, vaultBalance);
targetData[1] = abi.encodeWithSelector(myLeafs[1].selector, address(boringVault), true);

values[0] = 0;
values[1] = vaultBalance;

// console.log("myLeafs[0]");
// console.log(myLeafs[0].decoderAndSanitizer);
// console.log(myLeafs[0].target);
// console.logBytes(myLeafs[0].packedArgumentAddresses);
// console.logBytes4(myLeafs[0].selector);
// console.log(myLeafs[0].valueNonZero);
// console.log("targetData[0]");
// console.log(decodersAndSanitizers[0]);
// console.log(targets[0]);
// console.logBytes(targetData[0]);
// console.log(values[0]);
// console.log("As _verifyManageProof");
// bytes memory vmp_packedArgumentAddresses =
// abi.decode((decodersAndSanitizers[0]).functionStaticCall(targetData[0]), (bytes));
// console.log(decodersAndSanitizers[0]);
// console.log(targets[0]);
// console.log(values[0]);
// console.logBytes4(bytes4(targetData[0]));
// console.logBytes(vmp_packedArgumentAddresses);
vm.prank(ADMIN);
manager.manageVaultWithMerkleVerification(manageProofs, decodersAndSanitizers, targets, targetData, values);
assertGe(ERC20(APX_ETH).balanceOf(address(boringVault)), TARGET_APX_ETH, "APX_ETH balance is less than target");
}

function _setUpManager(uint256 depositAmount) internal {
vm.startPrank(ADMIN);
// admin allows strategy for deposit to APX ETH returning tokens to boring vault
Leaf[] memory myLeafs = _getLeafsForTest(depositAmount);
buildExampleTree(myLeafs);
bytes32 root = _getRoot();

manager.setManageRoot(ADMIN, root);
vm.stopPrank();
}

function _getLeafsForTest(uint256 depositAmount) internal returns (Leaf[] memory myLeafs) {
// weth -> eth
Leaf memory wethForEthLeaf =
Leaf(address(decoder), WETH, false, NativeWrapperDecoderAndSanitizer.withdraw.selector, "");

// eth -> pirexETH for apxETH
bytes memory packedArguments = abi.encodePacked(address(boringVault));
Leaf memory pirexEthLeaf =
Leaf(address(decoder), PIREX_ETH, true, PirexEthDecoderAndSanitizer.deposit.selector, packedArguments);
myLeafs = new Leaf[](2);

myLeafs[0] = wethForEthLeaf;
myLeafs[1] = pirexEthLeaf;
}
}
18 changes: 11 additions & 7 deletions test/strategy/StrategyBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@ abstract contract StrategyBase is Test {

function setUpDecoderSanitizers() public virtual;

function buildExampleTree(Leaf memory leaf) public {
Leaf[] memory leafs = new Leaf[](EXAMPLE_TREE_SIZE);
// users leaf is first one
leafs[0] = leaf;
function buildExampleTree(Leaf[] memory leafsIn) public {
require(leafsIn.length <= EXAMPLE_TREE_SIZE, "Cannot provide more leafs than EXAMPLE TREE SIZE");
Leaf[] memory leafsOut = new Leaf[](EXAMPLE_TREE_SIZE);

// set the provided leafs in
for (uint256 i; i < leafsIn.length; ++i) {
leafsOut[i] = leafsIn[i];
}
// fill others with random data
for (uint256 i = 1; i < EXAMPLE_TREE_SIZE; ++i) {
leafs[i] = Leaf(
for (uint256 i = leafsIn.length; i < EXAMPLE_TREE_SIZE; ++i) {
leafsOut[i] = Leaf(
address(bytes20(bytes32(i))), address(bytes20(bytes32((i * EXAMPLE_TREE_SIZE)))), false, 0x00000000, ""
);
}
tree = _generateMerkleTree(leafs);
tree = _generateMerkleTree(leafsOut);
}

function _hashLeaf(Leaf memory leaf) internal returns (bytes32 leafHash) {
Expand Down

0 comments on commit 0ecc737

Please sign in to comment.