Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/plantuml/ethenaProcesses.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions docs/plantuml/ethenaProcesses.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@

@startuml

title "Ethena ARM Processes"

actor "Operator" as op
actor "Anyone" as any
' participant "Ethena\nAPI" as api <<Ethena>>

box Blockchain
participant "Ethena\nARM" as arm <<Origin>>
participant "Ethena\nUnstaker" as unstaker <<Origin>>
participant "Ethena\nMinting" as mint <<Ethena>>
participant "sUSDe\nToken" as susde <<Ethena>>
participant "USDe\nToken" as usde <<Ethena>>
' participant "USDT\nToken" as usdt <<Tether>>
end box

group unstake sUSDe

op -> arm : requestBaseWithdrawal(\n base amount)
activate arm

' arm -> factory : assignHelper()
' activate factory

note over arm: increment unstaker index

arm -> mint : cooldowns(\n unstaker)
activate mint
return liquid amount

arm -> susde : transfer(\n unstaker,\n base amount)
activate susde
note right: transfer sUSDe\nfrom ARM\nto unstaker
return

arm -> unstaker : requestUnstake(\n base amount)
activate unstaker
unstaker -> susde : cooldownShares(\n base amount)
activate susde
note right
burn sUSDe from unstaker
increment underlying USDe
restart 7 day cooldown
end note
return liquid amount
return liquid amount
return
note right: emit unstaker, base amount, liquid amount

... 7 day cooldown ...

any -> arm : claimBaseWithdrawals(\n unstaker)
activate arm

arm -> mint : cooldowns(\n unstaker)
activate mint
return liquid amount

arm -> unstaker : claimUnstake()
activate unstaker
unstaker -> susde : unstake(arm)
activate susde
note right: check cooldown passed
susde -> usde : transfer(\n arm,\n amount)
activate usde
note right: transfer all\nunderlying USDe\nto ARM
return
return
return
note right: emit unstaker, liquid amount
return

end group

' group redeem USDe for USDT

' op -> api : GET Request For Quote\npayload: {\n pair,\n side,\n size,\n benefactor}
' activate api
' return order details: {\n rfq_id,\n collateral_amount,\n amount...}

' op -> op : sign(hash(order details))
' note right: sign order hash using Operator's private key

' op -> arm : redeemEthenaRequest (\n order details,\n signature)
' activate arm
' note right
' verify redeem price >= cross price
' verify collateral asset = USDT
' store map of order hash to signature
' end note

' arm -> usde : approve(\n amount,\n Ethena Minting)
' activate usde
' return
' return

' op -> api : POST Submit Order\nparams:\n signature,\n signature_type=EIP1271\npayload: order details
' activate api
' api -> mint : redeem (\n order details,\n signature: {\n bytes, type: 1})
' activate mint
' mint -> arm : isValidSignature(\n order hash,\n signature bytes)
' activate arm
' return
' mint -> usde : burnFrom(\n arm,\n amount)
' activate usde
' note right: burn USDe from ARM
' return
' mint -> usdt : transfer(\n arm,\n amount)
' activate usdt
' note right: transfer USDT to ARM
' return
' return
' return tx hash

' end group
2 changes: 2 additions & 0 deletions script/deploy/DeployManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {UpgradeOriginARMSetBufferScript} from "./sonic/005_UpgradeOriginARMSetBu
import {UpgradeLidoARMAssetScript} from "./mainnet/010_UpgradeLidoARMAssetScript.sol";
import {DeployEtherFiARMScript} from "./mainnet/011_DeployEtherFiARMScript.sol";
import {UpgradeEtherFiARMScript} from "./mainnet/012_UpgradeEtherFiARMScript.sol";
import {DeployEthenaARMScript} from "./mainnet/014_DeployEthenaARMScript.sol";

contract DeployManager is Script {
using stdJson for string;
Expand Down Expand Up @@ -86,6 +87,7 @@ contract DeployManager is Script {
_runDeployFile(new UpgradeLidoARMAssetScript());
_runDeployFile(new DeployEtherFiARMScript());
_runDeployFile(new UpgradeEtherFiARMScript());
_runDeployFile(new DeployEthenaARMScript());
} else if (block.chainid == 17000) {
// Holesky
_runDeployFile(new DeployCoreHoleskyScript());
Expand Down
145 changes: 145 additions & 0 deletions script/deploy/mainnet/014_DeployEthenaARMScript.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

// Foundry imports
import {console} from "forge-std/console.sol";

// Contract imports
import {Proxy} from "contracts/Proxy.sol";
import {Mainnet} from "contracts/utils/Addresses.sol";
import {EthenaARM} from "contracts/EthenaARM.sol";
import {CapManager} from "contracts/CapManager.sol";
import {MorphoMarket} from "contracts/markets/MorphoMarket.sol";
import {EthenaUnstaker} from "contracts/EthenaARM.sol";
import {IWETH, IStakedUSDe} from "contracts/Interfaces.sol";
import {Abstract4626MarketWrapper} from "contracts/markets/Abstract4626MarketWrapper.sol";

// Deployment imports
import {GovProposal, GovSixHelper} from "contracts/utils/GovSixHelper.sol";
import {AbstractDeployScript} from "../AbstractDeployScript.sol";

contract DeployEthenaARMScript is AbstractDeployScript {
using GovSixHelper for GovProposal;

GovProposal public govProposal;

string public constant override DEPLOY_NAME = "014_DeployEthenaARMScript";
bool public constant override proposalExecuted = true;

Proxy morphoMarketProxy;
EthenaARM armImpl;
MorphoMarket morphoMarket;
Proxy armProxy;

uint256 public constant MAX_UNSTAKERS = 42;

function _execute() internal override {
console.log("Deploy:", DEPLOY_NAME);
console.log("------------");

// 1. Deploy new ARM proxy contract
armProxy = new Proxy();
_recordDeploy("ETHENA_ARM", address(armProxy));

// 2. Deploy proxy for the CapManager
Proxy capManProxy = new Proxy();
_recordDeploy("ETHENA_ARM_CAP_MAN", address(capManProxy));

// 3. Deploy CapManager implementation
CapManager capManagerImpl = new CapManager(address(armProxy));
_recordDeploy("ETHENA_ARM_CAP_IMPL", address(capManagerImpl));

// 4. Initialize Proxy with CapManager implementation and set the owner to the deployer for now
bytes memory capManData = abi.encodeWithSignature("initialize(address)", Mainnet.ARM_RELAYER);
capManProxy.initialize(address(capManagerImpl), deployer, capManData);
CapManager capManager = CapManager(address(capManProxy));

// 4. Set total assets and liquidity provider caps
capManager.setTotalAssetsCap(250 ether);
capManager.setAccountCapEnabled(true);
address[] memory lpAccounts = new address[](1);
lpAccounts[0] = Mainnet.TREASURY_LP;
capManager.setLiquidityProviderCaps(lpAccounts, 250 ether);

// 5. Transfer ownership of CapManager to the mainnet 5/8 multisig
capManProxy.setOwner(Mainnet.GOV_MULTISIG);

// 6. Deploy new Ethena implementation
uint256 claimDelay = tenderlyTestnet ? 1 minutes : 10 minutes;
armImpl = new EthenaARM(
Mainnet.USDE,
Mainnet.SUSDE,
claimDelay,
1e7, // minSharesToRedeem
1e18 // allocateThreshold
);
_recordDeploy("ETHENA_ARM_IMPL", address(armImpl));

// 7. Give the deployer a tiny amount of USDe for the initialization
// This can be skipped if the deployer already has USDe
IWETH(Mainnet.USDE).approve(address(armProxy), 1e13);

// 8. Initialize proxy, set the owner to deployer, set the operator to the ARM Relayer
bytes memory armData = abi.encodeWithSignature(
"initialize(string,string,address,uint256,address,address)",
"Ethena Staked USDe ARM", // name
"ARM-sUSDe-USDe", // symbol
Mainnet.ARM_RELAYER, // Operator
2000, // 20% performance fee
Mainnet.BUYBACK_OPERATOR, // Fee collector
address(capManager)
);
armProxy.initialize(address(armImpl), deployer, armData);

console.log("Initialized Ethena ARM");

// 10. Deploy MorphoMarket proxy
morphoMarketProxy = new Proxy();
_recordDeploy("MORPHO_MARKET_ETHENA", address(morphoMarketProxy));

// 11. Deploy MorphoMarket
morphoMarket = new MorphoMarket(address(armProxy), Mainnet.MORPHO_MARKET_ETHENA);
_recordDeploy("MORPHO_MARKET_ETHENA_IMPL", address(morphoMarket));

// 12. Initialize MorphoMarket proxy with the implementation, Timelock as owner
bytes memory data = abi.encodeWithSelector(
Abstract4626MarketWrapper.initialize.selector, Mainnet.STRATEGIST, Mainnet.MERKLE_DISTRIBUTOR
);
morphoMarketProxy.initialize(address(morphoMarket), Mainnet.TIMELOCK, data);

// 13. Set crossPrice to 0.9998 USDe
uint256 crossPrice = 0.9998 * 1e36;
EthenaARM(payable(address(armProxy))).setCrossPrice(crossPrice);

// 14. Add Morpho Market as an active market
address[] memory markets = new address[](1);
markets[0] = address(morphoMarketProxy);
EthenaARM(payable(address(armProxy))).addMarkets(markets);

// 15. Set Morpho Market as the active market
EthenaARM(payable(address(armProxy))).setActiveMarket(address(morphoMarketProxy));

// 16. Set ARM buffer to 10%
EthenaARM(payable(address(armProxy))).setARMBuffer(0.1e18); // 10% buffer

// 17. Deploy Unstakers
address[MAX_UNSTAKERS] memory unstakers = _deployUnstakers();

// 18. Set Unstakers in the ARM
EthenaARM(payable(address(armProxy))).setUnstakers(unstakers);

// 19. Transfer ownership of ARM to the 5/8 multisig
armProxy.setOwner(Mainnet.GOV_MULTISIG);

console.log("Finished deploying", DEPLOY_NAME);
}

function _deployUnstakers() internal returns (address[MAX_UNSTAKERS] memory unstakers) {
for (uint256 i = 0; i < MAX_UNSTAKERS; i++) {
address unstaker = address(new EthenaUnstaker(payable(armProxy), IStakedUSDe(Mainnet.SUSDE)));
unstakers[i] = address(unstaker);
console.log("Deployed unstaker", i, address(unstaker));
}
return unstakers;
}
}
Loading
Loading