Skip to content

Commit

Permalink
Merge pull request #25 from tanim0la/feature/eng-19-strategy-module-d…
Browse files Browse the repository at this point in the history
…evelopment

Implemented the Exit Method and other action type in the moonwell connector
  • Loading branch information
njokuScript authored Dec 23, 2024
2 parents 8cebd6e + 5402aaa commit 826b18d
Show file tree
Hide file tree
Showing 11 changed files with 535 additions and 266 deletions.
5 changes: 3 additions & 2 deletions script/deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ contract DeployScript is Script {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

Strategy strategy = new Strategy();
Engine engine = new Engine(address(strategy));
Engine engine = new Engine();
Strategy strategy = new Strategy(address(engine));

Oracle oracle = new Oracle();

MoonwellConnector mwConnector = new MoonwellConnector(
Expand Down
20 changes: 14 additions & 6 deletions src/BaseConnector.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,28 @@ abstract contract BaseConnector is IConnector {

/**
* @notice Executes a function call on the connected connector
* @dev This function must be implemented by derived contracts
* @param actionType The Core actions that a connector can perform
* @param data The calldata for the function call containing the parameters
* @return amountOut The amount out from the function call
*/
function execute(
ActionType actionType,
address[] memory assetsIn,
uint256[] memory amounts,
address assetOut,
uint256 stepIndex,
uint256 amountRatio,
uint256 prevLoopAmountOut,
bytes32 strategyId,
address userAddress,
bytes calldata data
) external payable virtual returns (uint256 amountOut);
)
external
payable
virtual
returns (
address protocol,
address[] memory assets,
uint256[] memory assetsAmount,
address shareToken,
uint256 shareAmount,
address[] memory underlyingTokens,
uint256[] memory underlyingAmounts
);
}
175 changes: 136 additions & 39 deletions src/curators/engine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import "./interface/IStrategy.sol";
import {ERC4626, ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";

contract Engine is ERC4626 {
// might change later
ILiquidStrategy strategyModule;

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
Expand All @@ -21,65 +18,98 @@ contract Engine is ERC4626 {
*/
event Join(bytes32 indexed strategyId, address indexed depositor, address[] tokenAddress, uint256[] amount);

constructor(address _strategyModule) ERC4626(IERC20(address(this))) ERC20("LIQUID", "LLP") {
strategyModule = ILiquidStrategy(_strategyModule);
}
/**
* @dev Emitted when a user joins a strategy
* @param strategyId unique identifier for the strategy
* @param user address of the user exiting the strategy
*/
event Exit(bytes32 indexed strategyId, address indexed user);

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERROR */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

error InvalidActionType();

constructor() ERC4626(IERC20(address(this))) ERC20("LIQUID", "LLP") {}

function join(bytes32 _strategyId, address _strategyModule, uint256[] memory _amounts) public {
// restrict to join a strategy once

function join(bytes32 _strategyId, uint256[] memory amounts) public {
// Fetch the strategy
ILiquidStrategy.Strategy memory _strategy = strategyModule.getStrategy(_strategyId);
ILiquidStrategy.Strategy memory strategy = ILiquidStrategy(_strategyModule).getStrategy(_strategyId);

// Validate strategy - not necessary single we validate the steps before strategy creation

// Transfer initial deposit(s) from caller
uint256 initialAssetsInLength = _strategy.steps[0].assetsIn.length;
uint256 initialAssetsInLength = strategy.steps[0].assetsIn.length;

for (uint256 i; i < initialAssetsInLength; i++) {
address asset = _strategy.steps[0].assetsIn[i];
address asset = strategy.steps[0].assetsIn[i];
// approve `this` as spender in client first
ERC4626(asset).transferFrom(msg.sender, address(this), amounts[i]);
ERC4626(asset).transferFrom(msg.sender, address(this), _amounts[i]);
// tranfer token to connector
ERC4626(asset).transfer(_strategy.steps[0].connector, amounts[i]);
ERC4626(asset).transfer(strategy.steps[0].connector, _amounts[i]);
}

uint256 prevLoopAmountOut;
uint256[] memory prevAmounts = _amounts;

// Execute all steps atomically
for (uint256 i; i < _strategy.steps.length; i++) {
for (uint256 i; i < strategy.steps.length; i++) {
// Fetch step
ILiquidStrategy.Step memory _step = _strategy.steps[i];
ILiquidStrategy.Step memory step = strategy.steps[i];

// Default ratio to 100% for first step
uint256 _amountRatio = i == 0 ? 10_000 : _step.amountRatio;
uint256 amountRatio = i == 0 ? 10_000 : step.amountRatio;

// Constrain the first step to certain actions
if (
i == 0
&& (
(_step.actionType == IConnector.ActionType.BORROW)
|| (_step.actionType == IConnector.ActionType.UNSTAKE)
|| (_step.actionType == IConnector.ActionType.WITHDRAW)
|| (_step.actionType == IConnector.ActionType.REPAY)
(step.actionType == IConnector.ActionType.BORROW)
|| (step.actionType == IConnector.ActionType.UNSTAKE)
|| (step.actionType == IConnector.ActionType.WITHDRAW)
|| (step.actionType == IConnector.ActionType.REPAY)
)
) revert();

// Execute connector action
try IConnector(_step.connector).execute(
_step.actionType,
_step.assetsIn,
amounts,
_step.assetOut,
_amountRatio,
prevLoopAmountOut,
try IConnector(step.connector).execute(
step.actionType,
step.assetsIn,
prevAmounts,
step.assetOut,
type(uint256).max,
amountRatio,
_strategyId,
msg.sender,
_step.data
) returns (uint256 amountOut) {
step.data
) returns (
address protocol,
address[] memory assets,
uint256[] memory assetsAmount,
address shareToken,
uint256 shareAmount,
address[] memory underlyingTokens,
uint256[] memory underlyingAmounts
) {
// Verify result
// require(verifyResult(amountOut, _step.assetOut, _step.connector), "Invalid result");

prevLoopAmountOut = amountOut;

// Update the strategy module
require(_verifyResult(shareAmount, step.assetOut, _strategyModule), "Invalid shareAmount");

// update user info
ILiquidStrategy(_strategyModule).updateUserStats(
_strategyId,
msg.sender,
protocol,
assets,
assetsAmount,
shareToken,
shareAmount,
underlyingTokens,
underlyingAmounts
);

prevAmounts = assetsAmount;
} catch Error(string memory reason) {
revert(string(abi.encodePacked("Step ", i, " failed: ", reason)));
}
Expand All @@ -89,14 +119,81 @@ contract Engine is ERC4626 {
uint256 _fee = 0;

// update strategy stats
strategyModule.updateStrategyStats(_strategyId, amounts, _fee);
ILiquidStrategy(_strategyModule).updateStrategyStats(_strategyId, strategy.steps[0].assetsIn, _amounts, _fee);

// update user's strategy array
ILiquidStrategy(_strategyModule).updateUserStrategy(_strategyId, msg.sender, 0);

// Emits Join event
emit Join(_strategyId, msg.sender, _strategy.steps[0].assetsIn, amounts);
emit Join(_strategyId, msg.sender, strategy.steps[0].assetsIn, _amounts);
}

function exit(bytes32 _strategyId, address _strategyModule) public {
// check and burn user's liquid share token (also prevent re-enterancy)

// Fetch the strategy
ILiquidStrategy.Step[] memory steps = ILiquidStrategy(_strategyModule).getStrategy(_strategyId).steps;

// Execute all steps in reverse atomically
for (uint256 i = steps.length; i > 0; i--) {
// Fetch step
ILiquidStrategy.Step memory step = steps[i - 1];

address[] memory assetsIn;
address assetOut;
// Flip action type (unsure if repay and withdraw would be part of strategy steps)
IConnector.ActionType actionType;
if (step.actionType == IConnector.ActionType.SUPPLY) {
actionType = IConnector.ActionType.WITHDRAW;

// asset in
assetsIn = new address[](1);
assetsIn[0] = step.assetOut;

// asset out
assetOut = step.assetsIn[0];
} else if (step.actionType == IConnector.ActionType.BORROW) {
actionType = IConnector.ActionType.REPAY;

// asset in
assetsIn = new address[](3);
assetsIn[0] = step.assetOut;
assetsIn[1] = step.assetsIn[1];
assetsIn[2] = step.assetsIn[2];
} else {
revert InvalidActionType();
}

// Execute connector action
try IConnector(step.connector).execute(
actionType, assetsIn, new uint256[](0), assetOut, i - 1, 0, _strategyId, msg.sender, step.data
) returns (
address protocol,
address[] memory assets,
uint256[] memory assetsAmount,
address shareToken,
uint256 shareAmount,
address[] memory underlyingTokens,
uint256[] memory underlyingAmounts
) {
// Some checks here
} catch Error(string memory reason) {
revert(string(abi.encodePacked("Step ", i, " failed: ", reason)));
}
}

// update user strategy stats
ILiquidStrategy(_strategyModule).updateUserStrategy(_strategyId, msg.sender, 1);

// Emits Exit event
emit Exit(_strategyId, msg.sender);
}

function verifyResult(uint256 _amountOut, address _assetOut, address _connector) internal view returns (bool) {
// return ERC4626(_assetOut).balanceOf(address(this)) == _amountOut;
return ERC4626(_assetOut).balanceOf(_connector) == _amountOut;
function _verifyResult(uint256 _shareAmount, address _assetOut, address _strategyModule)
internal
view
returns (bool)
{
return ERC4626(_assetOut).balanceOf(_strategyModule) == _shareAmount;
}
}
4 changes: 3 additions & 1 deletion src/curators/interface/IEngine.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

interface IEngine {}
interface IEngine {
function join(bytes32 _strategyId, address _strategyModule, uint256[] memory _amounts) external;
}
Loading

0 comments on commit 826b18d

Please sign in to comment.