Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/proposals/20251002/KeelEthereum_20251002.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.25;
import {Ethereum, KeelPayloadEthereum} from "src/libraries/KeelPayloadEthereum.sol";
import {MainnetControllerInit, ControllerInstance} from "lib/keel-alm-controller/deploy/MainnetControllerInit.sol";
import {MainnetController} from "lib/keel-alm-controller/src/MainnetController.sol";
/**
* @title October 02, 2025 Keel Ethereum Proposal
* @notice Initialize Keel Mainnet ALM Controller
* @author Exo Tech
* Forum: https://forum.sky.money/t/october-02-2025-prime-technical-scope-keel-initialization-for-upcoming-spell/27192
* Vote: https://vote.sky.money/polling/QmWfqZRS
*/
contract KeelEthereum_20251002 is KeelPayloadEthereum {
function _execute() internal override {
_initiateAlmSystem();
_setupBasicRateLimits();
_onboardSusdsVault();
}
function _initiateAlmSystem() internal {
MainnetControllerInit.MintRecipient[] memory mintRecipients = new MainnetControllerInit.MintRecipient[](0);
MainnetControllerInit.LayerZeroRecipient[] memory layerZeroRecipients =
new MainnetControllerInit.LayerZeroRecipient[](0);
MainnetControllerInit.initAlmSystem({
vault: Ethereum.ALLOCATOR_VAULT,
usds: Ethereum.USDS,
controllerInst: ControllerInstance({
almProxy: Ethereum.ALM_PROXY,
controller: Ethereum.ALM_CONTROLLER,
rateLimits: Ethereum.ALM_RATE_LIMITS
}),
configAddresses: MainnetControllerInit.ConfigAddressParams({
freezer: Ethereum.ALM_FREEZER,
relayers: _createRelayersArray(),
oldController: address(0)
}),
checkAddresses: MainnetControllerInit.CheckAddressParams({
admin: Ethereum.KEEL_PROXY,
proxy: Ethereum.ALM_PROXY,
rateLimits: Ethereum.ALM_RATE_LIMITS,
vault: Ethereum.ALLOCATOR_VAULT,
psm: Ethereum.PSM,
daiUsds: Ethereum.DAI_USDS,
cctp: Ethereum.CCTP_TOKEN_MESSENGER
}),
mintRecipients: mintRecipients,
layerZeroRecipients: layerZeroRecipients
});
}
function _setupBasicRateLimits() private {
// Update USDS RateLimit
// before: 0
// After: 10k
_setUSDSMintRateLimit(10_000e18, 5_000e18 / uint256(1 days));
// Update USDS <> USDC RateLimit
// before: 0
// After: 10k
_setUSDSToUSDCRateLimit(10_000e6, 5_000e6 / uint256(1 days));
}
function _onboardSusdsVault() private {
// Update sUSDS RateLimit
// before: 0
// After: 10k
_onboardERC4626Vault(Ethereum.SUSDS, 10_000e18, 5_000e18 / uint256(1 days));
}
function _createRelayersArray() private pure returns (address[] memory) {
address[] memory relayers = new address[](2);
relayers[0] = Ethereum.ALM_RELAYER;
// Keel Relayer C (Backup)
relayers[1] = Ethereum.ALM_RELAYER_BACKUP;
return relayers;
}
}
"
},
"src/libraries/KeelPayloadEthereum.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
import {Ethereum} from "lib/keel-address-registry/src/Ethereum.sol";
import {KeelLiquidityLayerHelpers} from "./KeelLiquidityLayerHelpers.sol";
/**
* @dev Base smart contract for Ethereum.
* @author Exo Tech
*/
abstract contract KeelPayloadEthereum {
function execute() external {
_execute();
}
function _execute() internal virtual;
function _onboardERC4626Vault(address vault, uint256 depositMax, uint256 depositSlope) internal {
KeelLiquidityLayerHelpers.onboardERC4626Vault(Ethereum.ALM_RATE_LIMITS, vault, depositMax, depositSlope);
}
function _setUSDSMintRateLimit(uint256 maxAmount, uint256 slope) internal {
KeelLiquidityLayerHelpers.setUSDSMintRateLimit(Ethereum.ALM_RATE_LIMITS, maxAmount, slope);
}
function _setUSDSToUSDCRateLimit(uint256 maxAmount, uint256 slope) internal {
KeelLiquidityLayerHelpers.setUSDSToUSDCRateLimit(Ethereum.ALM_RATE_LIMITS, maxAmount, slope);
}
}
"
},
"lib/keel-alm-controller/deploy/MainnetControllerInit.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.8.0;
import { MainnetController } from "../src/MainnetController.sol";
import { IALMProxy } from "../src/interfaces/IALMProxy.sol";
import { IRateLimits } from "../src/interfaces/IRateLimits.sol";
import { ControllerInstance } from "./ControllerInstance.sol";
interface IBufferLike {
function approve(address, address, uint256) external;
}
interface IPSMLike {
function kiss(address) external;
}
interface IVaultLike {
function buffer() external view returns (address);
function rely(address) external;
}
library MainnetControllerInit {
/**********************************************************************************************/
/*** Structs and constants ***/
/**********************************************************************************************/
struct CheckAddressParams {
address admin;
address proxy;
address rateLimits;
address vault;
address psm;
address daiUsds;
address cctp;
}
struct ConfigAddressParams {
address freezer;
address[] relayers;
address oldController;
}
struct MintRecipient {
uint32 domain;
bytes32 mintRecipient;
}
struct LayerZeroRecipient {
uint32 destinationEndpointId;
bytes32 recipient;
}
bytes32 constant DEFAULT_ADMIN_ROLE = 0x00;
/**********************************************************************************************/
/*** Internal library functions ***/
/**********************************************************************************************/
function initAlmSystem(
address vault,
address usds,
ControllerInstance memory controllerInst,
ConfigAddressParams memory configAddresses,
CheckAddressParams memory checkAddresses,
MintRecipient[] memory mintRecipients,
LayerZeroRecipient[] memory layerZeroRecipients
)
internal
{
// Step 1: Do sanity checks outside of the controller
require(IALMProxy(controllerInst.almProxy).hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "MainnetControllerInit/incorrect-admin-almProxy");
require(IRateLimits(controllerInst.rateLimits).hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "MainnetControllerInit/incorrect-admin-rateLimits");
// Step 2: Initialize the controller
_initController(controllerInst, configAddresses, checkAddresses, mintRecipients, layerZeroRecipients);
// Step 3: Configure almProxy within the allocation system
require(vault == checkAddresses.vault, "MainnetControllerInit/incorrect-vault");
IVaultLike(vault).rely(controllerInst.almProxy);
IBufferLike(IVaultLike(vault).buffer()).approve(usds, controllerInst.almProxy, type(uint256).max);
}
function upgradeController(
ControllerInstance memory controllerInst,
ConfigAddressParams memory configAddresses,
CheckAddressParams memory checkAddresses,
MintRecipient[] memory mintRecipients,
LayerZeroRecipient[] memory layerZeroRecipients
)
internal
{
_initController(controllerInst, configAddresses, checkAddresses, mintRecipients, layerZeroRecipients);
IALMProxy almProxy = IALMProxy(controllerInst.almProxy);
IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits);
require(configAddresses.oldController != address(0), "MainnetControllerInit/old-controller-zero-address");
require(almProxy.hasRole(almProxy.CONTROLLER(), configAddresses.oldController), "MainnetControllerInit/old-controller-not-almProxy-controller");
require(rateLimits.hasRole(rateLimits.CONTROLLER(), configAddresses.oldController), "MainnetControllerInit/old-controller-not-rateLimits-controller");
almProxy.revokeRole(almProxy.CONTROLLER(), configAddresses.oldController);
rateLimits.revokeRole(rateLimits.CONTROLLER(), configAddresses.oldController);
}
function pauseProxyInitAlmSystem(address psm, address almProxy) internal {
IPSMLike(psm).kiss(almProxy); // To allow using no fee functionality
}
/**********************************************************************************************/
/*** Private helper functions ***/
/**********************************************************************************************/
function _initController(
ControllerInstance memory controllerInst,
ConfigAddressParams memory configAddresses,
CheckAddressParams memory checkAddresses,
MintRecipient[] memory mintRecipients,
LayerZeroRecipient[] memory layerZeroRecipients
)
private
{
// Step 1: Perform controller sanity checks
MainnetController newController = MainnetController(controllerInst.controller);
require(newController.hasRole(DEFAULT_ADMIN_ROLE, checkAddresses.admin), "MainnetControllerInit/incorrect-admin-controller");
require(address(newController.proxy()) == controllerInst.almProxy, "MainnetControllerInit/incorrect-almProxy");
require(address(newController.rateLimits()) == controllerInst.rateLimits, "MainnetControllerInit/incorrect-rateLimits");
require(address(newController.vault()) == checkAddresses.vault, "MainnetControllerInit/incorrect-vault");
require(address(newController.psm()) == checkAddresses.psm, "MainnetControllerInit/incorrect-psm");
require(address(newController.daiUsds()) == checkAddresses.daiUsds, "MainnetControllerInit/incorrect-daiUsds");
require(address(newController.cctp()) == checkAddresses.cctp, "MainnetControllerInit/incorrect-cctp");
require(newController.psmTo18ConversionFactor() == 1e12, "MainnetControllerInit/incorrect-psmTo18ConversionFactor");
require(configAddresses.oldController != address(newController), "MainnetControllerInit/old-controller-is-new-controller");
// Step 2: Configure ACL permissions controller, almProxy, and rateLimits
IALMProxy almProxy = IALMProxy(controllerInst.almProxy);
IRateLimits rateLimits = IRateLimits(controllerInst.rateLimits);
almProxy.grantRole(almProxy.CONTROLLER(), address(newController));
newController.grantRole(newController.FREEZER(), configAddresses.freezer);
rateLimits.grantRole(rateLimits.CONTROLLER(), address(newController));
for (uint256 i; i < configAddresses.relayers.length; ++i) {
newController.grantRole(newController.RELAYER(), configAddresses.relayers[i]);
}
// Step 3: Configure the mint recipients on other domains
for (uint256 i; i < mintRecipients.length; ++i) {
newController.setMintRecipient(mintRecipients[i].domain, mintRecipients[i].mintRecipient);
}
// Step 4: Configure LayerZero recipients
for (uint256 i; i < layerZeroRecipients.length; ++i) {
newController.setLayerZeroRecipient(layerZeroRecipients[i].destinationEndpointId, layerZeroRecipients[i].recipient);
}
}
}
"
},
"lib/keel-alm-controller/src/MainnetController.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.21;
import { IAToken } from "aave-v3-origin/src/core/contracts/interfaces/IAToken.sol";
import { IPool as IAavePool } from "aave-v3-origin/src/core/contracts/interfaces/IPool.sol";
// This interface has been reviewed, and is compliant with the specs: https://eips.ethereum.org/EIPS/eip-7540
import { IERC7540 } from "forge-std/interfaces/IERC7540.sol";
import { AccessControl } from "openzeppelin-contracts/contracts/access/AccessControl.sol";
import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import { IERC4626 } from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
import { Ethereum } from "spark-address-registry/Ethereum.sol";
import { IALMProxy } from "./interfaces/IALMProxy.sol";
import { ICCTPLike } from "./interfaces/CCTPInterfaces.sol";
import { IRateLimits } from "./interfaces/IRateLimits.sol";
import "./interfaces/ILayerZero.sol";
import { CCTPLib } from "./libraries/CCTPLib.sol";
import { CurveLib } from "./libraries/CurveLib.sol";
import { IDaiUsdsLike, IPSMLike, PSMLib } from "./libraries/PSMLib.sol";
import { OptionsBuilder } from "layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";
import { RateLimitHelpers } from "./RateLimitHelpers.sol";
interface IATokenWithPool is IAToken {
function POOL() external view returns(address);
}
interface IEthenaMinterLike {
function setDelegatedSigner(address delegateSigner) external;
function removeDelegatedSigner(address delegateSigner) external;
}
interface ICentrifugeToken is IERC7540 {
function cancelDepositRequest(uint256 requestId, address controller) external;
function cancelRedeemRequest(uint256 requestId, address controller) external;
function claimCancelDepositRequest(uint256 requestId, address receiver, address controller)
external returns (uint256 assets);
function claimCancelRedeemRequest(uint256 requestId, address receiver, address controller)
external returns (uint256 shares);
}
interface IMapleTokenLike is IERC4626 {
function requestRedeem(uint256 shares, address receiver) external;
function removeShares(uint256 shares, address receiver) external;
}
interface IFarmLike {
function stake(uint256 amount) external;
function withdraw(uint256 amount) external;
function getReward() external;
}
interface ISUSDELike is IERC4626 {
function cooldownAssets(uint256 usdeAmount) external;
function cooldownShares(uint256 susdeAmount) external;
function unstake(address receiver) external;
}
interface IUSTBLike is IERC20 {
function subscribe(uint256 inAmount, address stablecoin) external;
}
interface IVaultLike {
function buffer() external view returns (address);
function draw(uint256 usdsAmount) external;
function wipe(uint256 usdsAmount) external;
}
interface ISparkVaultLike {
function take(uint256 assetAmount) external;
}
contract MainnetController is AccessControl {
using OptionsBuilder for bytes;
/**********************************************************************************************/
/*** Events ***/
/**********************************************************************************************/
event LayerZeroRecipientSet(uint32 indexed destinationEndpointId, bytes32 layerZeroRecipient);
event MaxSlippageSet(address indexed pool, uint256 maxSlippage);
event MintRecipientSet(uint32 indexed destinationDomain, bytes32 mintRecipient);
event RelayerRemoved(address indexed relayer);
/**********************************************************************************************/
/*** State variables ***/
/**********************************************************************************************/
bytes32 public FREEZER = keccak256("FREEZER");
bytes32 public RELAYER = keccak256("RELAYER");
bytes32 public LIMIT_4626_DEPOSIT = keccak256("LIMIT_4626_DEPOSIT");
bytes32 public LIMIT_4626_WITHDRAW = keccak256("LIMIT_4626_WITHDRAW");
bytes32 public LIMIT_7540_DEPOSIT = keccak256("LIMIT_7540_DEPOSIT");
bytes32 public LIMIT_7540_REDEEM = keccak256("LIMIT_7540_REDEEM");
bytes32 public LIMIT_AAVE_DEPOSIT = keccak256("LIMIT_AAVE_DEPOSIT");
bytes32 public LIMIT_AAVE_WITHDRAW = keccak256("LIMIT_AAVE_WITHDRAW");
bytes32 public LIMIT_ASSET_TRANSFER = keccak256("LIMIT_ASSET_TRANSFER");
bytes32 public LIMIT_CURVE_DEPOSIT = keccak256("LIMIT_CURVE_DEPOSIT");
bytes32 public LIMIT_CURVE_SWAP = keccak256("LIMIT_CURVE_SWAP");
bytes32 public LIMIT_CURVE_WITHDRAW = keccak256("LIMIT_CURVE_WITHDRAW");
bytes32 public LIMIT_LAYERZERO_TRANSFER = keccak256("LIMIT_LAYERZERO_TRANSFER");
bytes32 public LIMIT_MAPLE_REDEEM = keccak256("LIMIT_MAPLE_REDEEM");
bytes32 public LIMIT_FARM_DEPOSIT = keccak256("LIMIT_FARM_DEPOSIT");
bytes32 public LIMIT_FARM_WITHDRAW = keccak256("LIMIT_FARM_WITHDRAW");
bytes32 public LIMIT_SPARK_VAULT_TAKE = keccak256("LIMIT_SPARK_VAULT_TAKE");
bytes32 public LIMIT_SUPERSTATE_SUBSCRIBE = keccak256("LIMIT_SUPERSTATE_SUBSCRIBE");
bytes32 public LIMIT_SUSDE_COOLDOWN = keccak256("LIMIT_SUSDE_COOLDOWN");
bytes32 public LIMIT_USDC_TO_CCTP = keccak256("LIMIT_USDC_TO_CCTP");
bytes32 public LIMIT_USDC_TO_DOMAIN = keccak256("LIMIT_USDC_TO_DOMAIN");
bytes32 public LIMIT_USDE_BURN = keccak256("LIMIT_USDE_BURN");
bytes32 public LIMIT_USDE_MINT = keccak256("LIMIT_USDE_MINT");
bytes32 public LIMIT_USDS_MINT = keccak256("LIMIT_USDS_MINT");
bytes32 public LIMIT_USDS_TO_USDC = keccak256("LIMIT_USDS_TO_USDC");
uint256 internal CENTRIFUGE_REQUEST_ID = 0;
address public buffer;
IALMProxy public proxy;
ICCTPLike public cctp;
IDaiUsdsLike public daiUsds;
IEthenaMinterLike public ethenaMinter;
IPSMLike public psm;
IRateLimits public rateLimits;
IVaultLike public vault;
IERC20 public dai;
IERC20 public usds;
IERC20 public usde;
IERC20 public usdc;
IUSTBLike public ustb;
ISUSDELike public susde;
uint256 public psmTo18ConversionFactor;
mapping(address pool => uint256 maxSlippage) public maxSlippages; // 1e18 precision
mapping(uint32 destinationDomain => bytes32 mintRecipient) public mintRecipients;
mapping(uint32 destinationEndpointId => bytes32 layerZeroRecipient) public layerZeroRecipients;
/**********************************************************************************************/
/*** Initialization ***/
/**********************************************************************************************/
constructor(
address admin_,
address proxy_,
address rateLimits_,
address vault_,
address psm_,
address daiUsds_,
address cctp_
) {
_grantRole(DEFAULT_ADMIN_ROLE, admin_);
proxy = IALMProxy(proxy_);
rateLimits = IRateLimits(rateLimits_);
vault = IVaultLike(vault_);
buffer = IVaultLike(vault_).buffer();
psm = IPSMLike(psm_);
daiUsds = IDaiUsdsLike(daiUsds_);
cctp = ICCTPLike(cctp_);
ethenaMinter = IEthenaMinterLike(Ethereum.ETHENA_MINTER);
susde = ISUSDELike(Ethereum.SUSDE);
ustb = IUSTBLike(Ethereum.USTB);
dai = IERC20(daiUsds.dai());
usdc = IERC20(psm.gem());
usds = IERC20(Ethereum.USDS);
usde = IERC20(Ethereum.USDE);
psmTo18ConversionFactor = psm.to18ConversionFactor();
}
/**********************************************************************************************/
/*** Admin functions ***/
/**********************************************************************************************/
function setMintRecipient(uint32 destinationDomain, bytes32 mintRecipient) external {
_checkRole(DEFAULT_ADMIN_ROLE);
mintRecipients[destinationDomain] = mintRecipient;
emit MintRecipientSet(destinationDomain, mintRecipient);
}
function setLayerZeroRecipient(
uint32 destinationEndpointId,
bytes32 layerZeroRecipient
)
external
{
_checkRole(DEFAULT_ADMIN_ROLE);
layerZeroRecipients[destinationEndpointId] = layerZeroRecipient;
emit LayerZeroRecipientSet(destinationEndpointId, layerZeroRecipient);
}
function setMaxSlippage(address pool, uint256 maxSlippage) external {
_checkRole(DEFAULT_ADMIN_ROLE);
maxSlippages[pool] = maxSlippage;
emit MaxSlippageSet(pool, maxSlippage);
}
/**********************************************************************************************/
/*** Freezer functions ***/
/**********************************************************************************************/
function removeRelayer(address relayer) external {
_checkRole(FREEZER);
_revokeRole(RELAYER, relayer);
emit RelayerRemoved(relayer);
}
/**********************************************************************************************/
/*** Relayer vault functions ***/
/**********************************************************************************************/
function mintUSDS(uint256 usdsAmount) external {
_checkRole(RELAYER);
_rateLimited(LIMIT_USDS_MINT, usdsAmount);
// Mint USDS into the buffer
proxy.doCall(
address(vault),
abi.encodeCall(vault.draw, (usdsAmount))
);
// Transfer USDS from the buffer to the proxy
proxy.doCall(
address(usds),
abi.encodeCall(usds.transferFrom, (buffer, address(proxy), usdsAmount))
);
}
function burnUSDS(uint256 usdsAmount) external {
_checkRole(RELAYER);
_cancelRateLimit(LIMIT_USDS_MINT, usdsAmount);
// Transfer USDS from the proxy to the buffer
proxy.doCall(
address(usds),
abi.encodeCall(usds.transfer, (buffer, usdsAmount))
);
// Burn USDS from the buffer
proxy.doCall(
address(vault),
abi.encodeCall(vault.wipe, (usdsAmount))
);
}
/**********************************************************************************************/
/*** Relayer ERC20 functions ***/
/**********************************************************************************************/
function transferAsset(address asset, address destination, uint256 amount) external {
_checkRole(RELAYER);
_rateLimited(
RateLimitHelpers.makeAssetDestinationKey(LIMIT_ASSET_TRANSFER, asset, destination),
amount
);
proxy.doCall(
asset,
abi.encodeCall(IERC20(asset).transfer, (destination, amount))
);
}
/**********************************************************************************************/
/*** Relayer ERC4626 functions ***/
/**********************************************************************************************/
function depositERC4626(address token, uint256 amount) external returns (uint256 shares) {
_checkRole(RELAYER);
_rateLimitedAsset(LIMIT_4626_DEPOSIT, token, amount);
// Note that whitelist is done by rate limits
IERC20 asset = IERC20(IERC4626(token).asset());
// Approve asset to token from the proxy (assumes the proxy has enough of the asset).
_approve(address(asset), token, amount);
// Deposit asset into the token, proxy receives token shares, decode the resulting shares
shares = abi.decode(
proxy.doCall(
token,
abi.encodeCall(IERC4626(token).deposit, (amount, address(proxy)))
),
(uint256)
);
}
function withdrawERC4626(address token, uint256 amount) external returns (uint256 shares) {
_checkRole(RELAYER);
_rateLimitedAsset(LIMIT_4626_WITHDRAW, token, amount);
// Withdraw asset from a token, decode resulting shares.
// Assumes proxy has adequate token shares.
shares = abi.decode(
proxy.doCall(
token,
abi.encodeCall(IERC4626(token).withdraw, (amount, address(proxy), address(proxy)))
),
(uint256)
);
_cancelRateLimit(RateLimitHelpers.makeAssetKey(LIMIT_4626_DEPOSIT, token), amount);
}
// NOTE: !!! Rate limited at end of function !!!
function redeemERC4626(address token, uint256 shares) external returns (uint256 assets) {
_checkRole(RELAYER);
// Redeem shares for assets from the token, decode the resulting assets.
// Assumes proxy has adequate token shares.
assets = abi.decode(
proxy.doCall(
token,
abi.encodeCall(IERC4626(token).redeem, (shares, address(proxy), address(proxy)))
),
(uint256)
);
rateLimits.triggerRateLimitDecrease(
RateLimitHelpers.makeAssetKey(LIMIT_4626_WITHDRAW, token),
assets
);
_cancelRateLimit(RateLimitHelpers.makeAssetKey(LIMIT_4626_DEPOSIT, token), assets);
}
/**********************************************************************************************/
/*** Relayer ERC7540 functions ***/
/**********************************************************************************************/
function requestDepositERC7540(address token, uint256 amount) external {
_checkRole(RELAYER);
_rateLimitedAsset(LIMIT_7540_DEPOSIT, token, amount);
// Note that whitelist is done by rate limits
IERC20 asset = IERC20(IERC7540(token).asset());
// Approve asset to vault from the proxy (assumes the proxy has enough of the asset).
_approve(address(asset), token, amount);
// Submit deposit request by transferring assets
proxy.doCall(
token,
abi.encodeCall(IERC7540(token).requestDeposit, (amount, address(proxy), address(proxy)))
);
}
function claimDepositERC7540(address token) external {
_checkRole(RELAYER);
_rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_7540_DEPOSIT, token));
uint256 shares = IERC7540(token).maxMint(address(proxy));
// Claim shares from the vault to the proxy
proxy.doCall(
token,
abi.encodeCall(IERC4626(token).mint, (shares, address(proxy)))
);
}
function requestRedeemERC7540(address token, uint256 shares) external {
_checkRole(RELAYER);
_rateLimitedAsset(
LIMIT_7540_REDEEM,
token,
IERC7540(token).convertToAssets(shares)
);
// Submit redeem request by transferring shares
proxy.doCall(
token,
abi.encodeCall(IERC7540(token).requestRedeem, (shares, address(proxy), address(proxy)))
);
}
function claimRedeemERC7540(address token) external {
_checkRole(RELAYER);
_rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_7540_REDEEM, token));
uint256 assets = IERC7540(token).maxWithdraw(address(proxy));
// Claim assets from the vault to the proxy
proxy.doCall(
token,
abi.encodeCall(IERC7540(token).withdraw, (assets, address(proxy), address(proxy)))
);
}
/**********************************************************************************************/
/*** Relayer Centrifuge functions ***/
/**********************************************************************************************/
// NOTE: These cancelation methods are compatible with ERC-7887
function cancelCentrifugeDepositRequest(address token) external {
_checkRole(RELAYER);
_rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_7540_DEPOSIT, token));
// NOTE: While the cancelation is pending, no new deposit request can be submitted
proxy.doCall(
token,
abi.encodeCall(
ICentrifugeToken(token).cancelDepositRequest,
(CENTRIFUGE_REQUEST_ID, address(proxy))
)
);
}
function claimCentrifugeCancelDepositRequest(address token) external {
_checkRole(RELAYER);
_rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_7540_DEPOSIT, token));
proxy.doCall(
token,
abi.encodeCall(
ICentrifugeToken(token).claimCancelDepositRequest,
(CENTRIFUGE_REQUEST_ID, address(proxy), address(proxy))
)
);
}
function cancelCentrifugeRedeemRequest(address token) external {
_checkRole(RELAYER);
_rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_7540_REDEEM, token));
// NOTE: While the cancelation is pending, no new redeem request can be submitted
proxy.doCall(
token,
abi.encodeCall(
ICentrifugeToken(token).cancelRedeemRequest,
(CENTRIFUGE_REQUEST_ID, address(proxy))
)
);
}
function claimCentrifugeCancelRedeemRequest(address token) external {
_checkRole(RELAYER);
_rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_7540_REDEEM, token));
proxy.doCall(
token,
abi.encodeCall(
ICentrifugeToken(token).claimCancelRedeemRequest,
(CENTRIFUGE_REQUEST_ID, address(proxy), address(proxy))
)
);
}
/**********************************************************************************************/
/*** Relayer Aave functions ***/
/**********************************************************************************************/
function depositAave(address aToken, uint256 amount) external {
_checkRole(RELAYER);
_rateLimitedAsset(LIMIT_AAVE_DEPOSIT, aToken, amount);
IERC20 underlying = IERC20(IATokenWithPool(aToken).UNDERLYING_ASSET_ADDRESS());
IAavePool pool = IAavePool(IATokenWithPool(aToken).POOL());
// Approve underlying to Aave pool from the proxy (assumes the proxy has enough underlying).
_approve(address(underlying), address(pool), amount);
// Deposit underlying into Aave pool, proxy receives aTokens
proxy.doCall(
address(pool),
abi.encodeCall(pool.supply, (address(underlying), amount, address(proxy), 0))
);
}
// NOTE: !!! Rate limited at end of function !!!
function withdrawAave(address aToken, uint256 amount)
external
returns (uint256 amountWithdrawn)
{
_checkRole(RELAYER);
IAavePool pool = IAavePool(IATokenWithPool(aToken).POOL());
// Withdraw underlying from Aave pool, decode resulting amount withdrawn.
// Assumes proxy has adequate aTokens.
amountWithdrawn = abi.decode(
proxy.doCall(
address(pool),
abi.encodeCall(
pool.withdraw,
(IATokenWithPool(aToken).UNDERLYING_ASSET_ADDRESS(), amount, address(proxy))
)
),
(uint256)
);
rateLimits.triggerRateLimitDecrease(
RateLimitHelpers.makeAssetKey(LIMIT_AAVE_WITHDRAW, aToken),
amountWithdrawn
);
_cancelRateLimit(
RateLimitHelpers.makeAssetKey(LIMIT_AAVE_DEPOSIT, aToken),
amountWithdrawn
);
}
/**********************************************************************************************/
/*** Relayer Curve StableSwap functions ***/
/**********************************************************************************************/
function swapCurve(
address pool,
uint256 inputIndex,
uint256 outputIndex,
uint256 amountIn,
uint256 minAmountOut
)
external returns (uint256 amountOut)
{
_checkRole(RELAYER);
amountOut = CurveLib.swap(CurveLib.SwapCurveParams({
proxy : proxy,
rateLimits : rateLimits,
pool : pool,
rateLimitId : LIMIT_CURVE_SWAP,
inputIndex : inputIndex,
outputIndex : outputIndex,
amountIn : amountIn,
minAmountOut : minAmountOut,
maxSlippage : maxSlippages[pool]
}));
}
function addLiquidityCurve(
address pool,
uint256[] memory depositAmounts,
uint256 minLpAmount
)
external returns (uint256 shares)
{
_checkRole(RELAYER);
shares = CurveLib.addLiquidity(CurveLib.AddLiquidityParams({
proxy : proxy,
rateLimits : rateLimits,
pool : pool,
addLiquidityRateLimitId : LIMIT_CURVE_DEPOSIT,
swapRateLimitId : LIMIT_CURVE_SWAP,
minLpAmount : minLpAmount,
maxSlippage : maxSlippages[pool],
depositAmounts : depositAmounts
}));
}
function removeLiquidityCurve(
address pool,
uint256 lpBurnAmount,
uint256[] memory minWithdrawAmounts
)
external returns (uint256[] memory withdrawnTokens)
{
_checkRole(RELAYER);
withdrawnTokens = CurveLib.removeLiquidity(CurveLib.RemoveLiquidityParams({
proxy : proxy,
rateLimits : rateLimits,
pool : pool,
rateLimitId : LIMIT_CURVE_WITHDRAW,
lpBurnAmount : lpBurnAmount,
minWithdrawAmounts : minWithdrawAmounts,
maxSlippage : maxSlippages[pool]
}));
}
/**********************************************************************************************/
/*** Relayer Ethena functions ***/
/**********************************************************************************************/
function setDelegatedSigner(address delegatedSigner) external {
_checkRole(RELAYER);
proxy.doCall(
address(ethenaMinter),
abi.encodeCall(ethenaMinter.setDelegatedSigner, (address(delegatedSigner)))
);
}
function removeDelegatedSigner(address delegatedSigner) external {
_checkRole(RELAYER);
proxy.doCall(
address(ethenaMinter),
abi.encodeCall(ethenaMinter.removeDelegatedSigner, (address(delegatedSigner)))
);
}
// Note that Ethena's mint/redeem per-block limits include other users
function prepareUSDeMint(uint256 usdcAmount) external {
_checkRole(RELAYER);
_rateLimited(LIMIT_USDE_MINT, usdcAmount);
_approve(address(usdc), address(ethenaMinter), usdcAmount);
}
function prepareUSDeBurn(uint256 usdeAmount) external {
_checkRole(RELAYER);
_rateLimited(LIMIT_USDE_BURN, usdeAmount);
_approve(address(usde), address(ethenaMinter), usdeAmount);
}
function cooldownAssetsSUSDe(uint256 usdeAmount) external {
_checkRole(RELAYER);
_rateLimited(LIMIT_SUSDE_COOLDOWN, usdeAmount);
proxy.doCall(
address(susde),
abi.encodeCall(susde.cooldownAssets, (usdeAmount))
);
}
// NOTE: !!! Rate limited at end of function !!!
function cooldownSharesSUSDe(uint256 susdeAmount)
external
returns (uint256 cooldownAmount)
{
_checkRole(RELAYER);
cooldownAmount = abi.decode(
proxy.doCall(
address(susde),
abi.encodeCall(susde.cooldownShares, (susdeAmount))
),
(uint256)
);
rateLimits.triggerRateLimitDecrease(LIMIT_SUSDE_COOLDOWN, cooldownAmount);
}
function unstakeSUSDe() external {
_checkRole(RELAYER);
proxy.doCall(
address(susde),
abi.encodeCall(susde.unstake, (address(proxy)))
);
}
/**********************************************************************************************/
/*** Relayer Maple functions ***/
/**********************************************************************************************/
function requestMapleRedemption(address mapleToken, uint256 shares) external {
_checkRole(RELAYER);
_rateLimitedAsset(
LIMIT_MAPLE_REDEEM,
mapleToken,
IMapleTokenLike(mapleToken).convertToAssets(shares)
);
proxy.doCall(
mapleToken,
abi.encodeCall(IMapleTokenLike(mapleToken).requestRedeem, (shares, address(proxy)))
);
}
function cancelMapleRedemption(address mapleToken, uint256 shares) external {
_checkRole(RELAYER);
_rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_MAPLE_REDEEM, mapleToken));
proxy.doCall(
mapleToken,
abi.encodeCall(IMapleTokenLike(mapleToken).removeShares, (shares, address(proxy)))
);
}
/**********************************************************************************************/
/*** Relayer Superstate functions ***/
/**********************************************************************************************/
function subscribeSuperstate(uint256 usdcAmount) external {
_checkRole(RELAYER);
_rateLimited(LIMIT_SUPERSTATE_SUBSCRIBE, usdcAmount);
_approve(address(usdc), address(ustb), usdcAmount);
proxy.doCall(
address(ustb),
abi.encodeCall(ustb.subscribe, (usdcAmount, address(usdc)))
);
}
/**********************************************************************************************/
/*** Relayer DaiUsds functions ***/
/**********************************************************************************************/
function swapUSDSToDAI(uint256 usdsAmount)
external
onlyRole(RELAYER)
{
// Approve USDS to DaiUsds migrator from the proxy (assumes the proxy has enough USDS)
_approve(address(usds), address(daiUsds), usdsAmount);
// Swap USDS to DAI 1:1
proxy.doCall(
address(daiUsds),
abi.encodeCall(daiUsds.usdsToDai, (address(proxy), usdsAmount))
);
}
function swapDAIToUSDS(uint256 daiAmount)
external
onlyRole(RELAYER)
{
// Approve DAI to DaiUsds migrator from the proxy (assumes the proxy has enough DAI)
_approve(address(dai), address(daiUsds), daiAmount);
// Swap DAI to USDS 1:1
proxy.doCall(
address(daiUsds),
abi.encodeCall(daiUsds.daiToUsds, (address(proxy), daiAmount))
);
}
/**********************************************************************************************/
/*** Relayer PSM functions ***/
/**********************************************************************************************/
// NOTE: The param `usdcAmount` is denominated in 1e6 precision to match how PSM uses
// USDC precision for both `buyGemNoFee` and `sellGemNoFee`
function swapUSDSToUSDC(uint256 usdcAmount) external {
_checkRole(RELAYER);
PSMLib.swapUSDSToUSDC(PSMLib.SwapUSDSToUSDCParams({
proxy : proxy,
rateLimits : rateLimits,
daiUsds : daiUsds,
psm : psm,
usds : usds,
dai : dai,
rateLimitId : LIMIT_USDS_TO_USDC,
usdcAmount : usdcAmount,
psmTo18ConversionFactor : psmTo18ConversionFactor
}));
}
function swapUSDCToUSDS(uint256 usdcAmount) external {
_checkRole(RELAYER);
PSMLib.swapUSDCToUSDS(PSMLib.SwapUSDCToUSDSParams({
proxy : proxy,
rateLimits : rateLimits,
daiUsds : daiUsds,
psm : psm,
dai : dai,
usdc : usdc,
rateLimitId : LIMIT_USDS_TO_USDC,
usdcAmount : usdcAmount,
psmTo18ConversionFactor : psmTo18ConversionFactor
}));
}
// NOTE: !!! This function was deployed without integration testing !!!
// KEEP RATE LIMIT AT ZERO until LayerZero dependencies are live and
// all functionality has been thoroughly integration tested.
function transferTokenLayerZero(
address oftAddress,
uint256 amount,
uint32 destinationEndpointId
)
external payable
{
_checkRole(RELAYER);
_rateLimited(
keccak256(abi.encode(LIMIT_LAYERZERO_TRANSFER, oftAddress, destinationEndpointId)),
amount
);
// NOTE: Full integration testing of this logic is not possible without OFTs with
// approvalRequired == false. Add integration testing for this case before
// using in production.
if (ILayerZero(oftAddress).approvalRequired()) {
_approve(ILayerZero(oftAddress).token(), oftAddress, amount);
}
bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200_000, 0);
SendParam memory sendParams = SendParam({
dstEid : destinationEndpointId,
to : layerZeroRecipients[destinationEndpointId],
amountLD : amount,
minAmountLD : 0,
extraOptions : options,
composeMsg : "",
oftCmd : ""
});
// Query the min amount received on the destination chain and set it.
( ,, OFTReceipt memory receipt ) = ILayerZero(oftAddress).quoteOFT(sendParams);
sendParams.minAmountLD = receipt.amountReceivedLD;
MessagingFee memory fee = ILayerZero(oftAddress).quoteSend(sendParams, false);
proxy.doCallWithValue{value: fee.nativeFee}(
oftAddress,
abi.encodeCall(ILayerZero.send, (sendParams, fee, address(proxy))),
fee.nativeFee
);
}
/**********************************************************************************************/
/*** Relayer bridging functions ***/
/**********************************************************************************************/
function transferUSDCToCCTP(uint256 usdcAmount, uint32 destinationDomain) external {
_checkRole(RELAYER);
CCTPLib.transferUSDCToCCTP(CCTPLib.TransferUSDCToCCTPParams({
proxy : proxy,
rateLimits : rateLimits,
cctp : cctp,
usdc : usdc,
domainRateLimitId : LIMIT_USDC_TO_DOMAIN,
cctpRateLimitId : LIMIT_USDC_TO_CCTP,
mintRecipient : mintRecipients[destinationDomain],
destinationDomain : destinationDomain,
usdcAmount : usdcAmount
}));
}
/**********************************************************************************************/
/*** Relayer SPK Farm functions ***/
/**********************************************************************************************/
function depositToFarm(address farm, uint256 usdsAmount) external {
_checkRole(RELAYER);
_rateLimited(
keccak256(abi.encode(LIMIT_FARM_DEPOSIT, farm)),
usdsAmount
);
_approve(address(usds), farm, usdsAmount);
proxy.doCall(
farm,
abi.encodeCall(IFarmLike.stake, (usdsAmount))
);
}
function withdrawFromFarm(address farm, uint256 usdsAmount) external {
_checkRole(RELAYER);
_rateLimited(
keccak256(abi.encode(LIMIT_FARM_WITHDRAW, farm)),
usdsAmount
);
proxy.doCall(
farm,
abi.encodeCall(IFarmLike.withdraw, (usdsAmount))
);
proxy.doCall(
farm,
abi.encodeCall(IFarmLike.getReward, ())
);
}
/**********************************************************************************************/
/*** Spark Vault functions ***/
/**********************************************************************************************/
function takeFromSparkVault(address sparkVault, uint256 assetAmount) external {
_checkRole(RELAYER);
_rateLimitedAsset(LIMIT_SPARK_VAULT_TAKE, sparkVault, assetAmount);
// Take assets from the vault
proxy.doCall(
sparkVault,
abi.encodeCall(ISparkVaultLike.take, (assetAmount))
);
}
/**********************************************************************************************/
/*** Relayer helper functions ***/
/**********************************************************************************************/
// NOTE: This logic was inspired by OpenZeppelin's forceApprove in SafeERC20 library
function _approve(address token, address spender, uint256 amount) internal {
bytes memory approveData = abi.encodeCall(IERC20.approve, (spender, amount));
// Call doCall on proxy to approve the token
( bool success, bytes memory data )
= address(proxy).call(abi.encodeCall(IALMProxy.doCall, (token, approveData)));
bytes memory approveCallReturnData;
if (success) {
// Data is the ABI-encoding of the approve call bytes return data, need to
// decode it first
approveCallReturnData = abi.decode(data, (bytes));
// Approve was successful if 1) no return value or 2) true return value
if (approveCallReturnData.length == 0 || abi.decode(approveCallReturnData, (bool))) {
return;
}
}
// If call was unsuccessful, set to zero and try again
proxy.doCall(token, abi.encodeCall(IERC20.approve, (spender, 0)));
approveCallReturnData = proxy.doCall(token, approveData);
// Revert if approve returns false
require(
approveCallReturnData.length == 0 || abi.decode(approveCallReturnData, (bool)),
"MainnetController/approve-failed"
);
}
/**********************************************************************************************/
/*** Rate Limit helper functions ***/
/**********************************************************************************************/
function _rateLimited(bytes32 key, uint256 amount) internal {
rateLimits.triggerRateLimitDecrease(key, amount);
}
function _rateLimitedAsset(bytes32 key, address asset, uint256 amount) internal {
rateLimits.triggerRateLimitDecrease(RateLimitHelpers.makeAssetKey(key, asset), amount);
}
function _cancelRateLimit(bytes32 key, uint256 amount) internal {
rateLimits.triggerRateLimitIncrease(key, amount);
}
function _rateLimitExists(bytes32 key) internal view {
require(
rateLimits.getRateLimitData(key).maxAmount > 0,
"MainnetController/invalid-action"
);
}
}
"
},
"lib/keel-address-registry/src/Ethereum.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.8.0;
library Ethereum {
/******************************************************************************************************************/
/*** Token Addresses ***/
/******************************************************************************************************************/
address internal constant CBBTC = 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf;
address internal constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address internal constant EZETH = 0xbf5495Efe5DB9ce00f80364C8B423567e58d2110;
address internal constant GNO = 0x6810e776880C02933D47DB1b9fc05908e5386b96;
address internal constant LBTC = 0x8236a87084f8B84306f72007F36F2618A5634494;
address internal constant MKR = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2;
address internal constant RETH = 0xae78736Cd615f374D3085123A210448E74Fc6393;
address internal constant RSETH = 0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7;
address internal constant SDAI = 0x83F20F44975D03b1b09e64809B757c47f942BEeA;
address internal constant SKY = 0x56072C95FAA701256059aa122697B133aDEd9279;
address internal constant SUSDC = 0xBc65ad17c5C0a2A4D159fa5a503f4992c7B545FE;
address internal constant SUSDE = 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497;
address internal constant SUSDS = 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD;
address internal constant TBTC = 0x18084fbA666a33d37592fA2633fD49a74DD93a88;
address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address internal constant USDE = 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3;
address internal constant USDS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F;
address internal constant USCC = 0x14d60E7FDC0D71d8611742720E4C50E7a974020c;
address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
address internal constant USTB = 0x43415eB6ff9DB7E26A15b704e7A3eDCe97d31C4e;
address internal constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
address internal constant WEETH = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee;
address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address internal constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
/******************************************************************************************************************/
/*** MakerDAO Addresses ***/
/******************************************************************************************************************/
address internal constant CHIEF = 0x929d9A1435662357F54AdcF64DcEE4d6b867a6f9;
address internal constant DAI_USDS = 0x3225737a9Bbb6473CB4a45b7244ACa2BeFdB276A;
address internal constant PAUSE_PROXY = 0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB;
address internal constant POT = 0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7;
address internal constant PSM = 0xf6e72Db5454dd049d0788e411b06CfAF16853042; // Lite PSM
address internal constant VAT = 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B;
/******************************************************************************************************************/
/*** KeelDAO Addresses ***/
/******************************************************************************************************************/
address internal constant KEEL_PROXY = 0x355CD90Ecb1b409Fdf8b64c4473C3B858dA2c310;
/******************************************************************************************************************/
/*** Allocation System Addresses ***/
/******************************************************************************************************************/
address internal constant ALLOCATOR_BUFFER = 0x065E5De3D3A08c9d14BF79Ce5A6d3D0E8794640c;
address internal constant ALLOCATOR_ORACLE = 0xc7B91C401C02B73CBdF424dFaaa60950d5040dB7;
address internal constant ALLOCATOR_REGISTRY = 0xCdCFA95343DA7821fdD01dc4d0AeDA958051bB3B;
address internal constant ALLOCATOR_ROLES = 0x9A865A710399cea85dbD9144b7a09C889e94E803;
address internal constant ALLOCATOR_VAULT = 0xe4470DD3158F7A905cDeA07260551F72d4bB0e77;
/******************************************************************************************************************/
/*** Keel Liquidity Layer Addresses ***/
/******************************************************************************************************************/
address internal constant ALM_CONTROLLER = 0xEF26BDc34F35669C235345aeF24A251B1EE80EF3;
address internal constant ALM_PROXY = 0xa5139956eC99aE2e51eA39d0b57C42B6D8db0758;
address internal constant ALM_RATE_LIMITS = 0x65E7B39e508944F7C4278d3e4580f84Eb20b26a7;
address internal constant ALM_FREEZER = 0xBCCB60cf518391d3315D63313F7bb764d02541fE;
address internal constant ALM_RELAYER = 0xA4F39dAae4Dc86c27c46b9a0605AE2c911451F95;
address internal constant ALM_RELAYER_BACKUP = 0x0f72935f6de6C54Ce8056FD040d4Ddb012B7cd54;
/******************************************************************************************************************/
/*** Cross-Domain Addresses ***/
/******************************************************************************************************************/
address internal constant CCTP_TOKEN_MESSENGER = 0xBd3fa81B58Ba92a82136038B25aDec7066af3155;
}
"
},
"src/libraries/KeelLiquidityLayerHelpers.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
import {RateLimitHelpers} from "lib/keel-alm-controller/src/RateLimitHelpers.sol";
import {IRateLimits} from "lib/keel-alm-controller/src/interfaces/IRateLimits.sol";
/**
* @notice Helper functions for the Keel Liquidity Layer
*/
library KeelLiquidityLayerHelpers {
bytes32 public constant LIMIT_4626_DEPOSIT = keccak256("LIMIT_4626_DEPOSIT");
bytes32 public constant LIMIT_4626_WITHDRAW = keccak256("LIMIT_4626_WITHDRAW");
bytes32 public constant LIMIT_USDS_MINT = keccak256("LIMIT_USDS_MINT");
bytes32 public constant LIMIT_USDS_TO_USDC = keccak256("LIMIT_USDS_TO_USDC");
/**
* @notice Onboard an ERC4626 vault
* @dev This will set the deposit to the given numbers with
* the withdraw limit set to unlimited.
*/
function onboardERC4626Vault(address rateLimits, address vault, uint256 depositMax, uint256 depositSlope)
internal
{
bytes32 depositKey = RateLimitHelpers.makeAssetKey(LIMIT_4626_DEPOSIT, vault);
bytes32 withdrawKey = RateLimitHelpers.makeAssetKey(LIMIT_4626_WITHDRAW, vault);
IRateLimits(rateLimits).setRateLimitData(depositKey, depositMax, depositSlope);
IRateLimits(rateLimits).setUnlimitedRateLimitData(withdrawKey);
}
/**
* @notice Set the USDS mint rate limit
* @param rateLimits The address of the rate limits contract
* @param maxAmount The maximum amount of USDS that can be minted
* @param slope The slope of the rate limit
*/
function setUSDSMintRateLimit(address rateLimits, uint256 maxAmount, uint256 slope) internal {
IRateLimits(rateLimits).setRateLimitData(LIMIT_USDS_MINT, maxAmount, slope);
}
/**
* @notice Set the USDS to USDC rate limit
* @param rateLimits The address of the rate limits contract
* @param maxAmount The maximum amount of USDC that can be minted
* @param slope The slope of the rate limit
*/
function setUSDSToUSDCRateLimit(address rateLimits, uint256 maxAmount, uint256 slope) internal {
IRateLimits(rateLimits).setRateLimitData(LIMIT_USDS_TO_USDC, maxAmount, slope);
}
}
"
},
"lib/keel-alm-controller/src/interfaces/IALMProxy.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.8.0;
import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol";
interface IALMProxy is IAccessControl {
/**
* @dev This function retrieves a constant `bytes32` value that represents the controller.
* @return The `bytes32` identifier of the controller.
*/
function CONTROLLER() external view returns (bytes32);
/**
* @dev Performs a standard call to the specified `target` with the given `data`.
* Reverts if the call fails.
* @param target The address of the target contract to call.
* @param data The calldata that will be sent to the target contract.
* @return result The returned data from the call.
*/
function doCall(address target, bytes calldata data)
external returns (bytes memory result);
/**
* @dev This function allows for transferring `value` (ether) along with the call to the target contract.
* Reverts if the call fails.
* @param target The address of the target contract to call.
* @param data The calldata that will be sent to the target contract.
* @param value The amount of Ether (in wei) to send with the call.
* @return result The returned data from the call.
*/
function doCallWithValue(address target, bytes memory data, uint256 value)
external payable returns (bytes memory result);
/**
* @dev This function performs a delegate call to the specified `target`
* with the given `data`. Reverts if the call fails.
* @param target The address of the target contract to delegate call.
* @param data The calldata that will be sent to the target contract.
* @return result The returned data from the delegate call.
*/
function doDelegateCall(address target, bytes calldata data)
external returns (bytes memory result);
}
"
},
"lib/keel-alm-controller/src/interfaces/IRateLimits.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.8.0;
import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol";
interface IRateLimits is IAccessControl {
/**********************************************************************************************/
/*** Structs ***/
/**********************************************************************************************/
/**
* @dev Struct representing a rate limit.
* The current rate limit is calculated using the formula:
* `currentRateLimit = min(slope * (block.timestamp - lastUpdated) + lastAmount, maxAmount)`.
* @param maxAmount Maximum allowed amount at any time.
* @param slope The slope of the rate limit, used to calculate the new
* limit based on time passed. [tokens / second]
* @param lastAmount The amount left available at the last update.
* @param lastUpdated The timestamp when the rate limit was last updated.
*/
struct RateLimitData {
uint256 maxAmount;
uint256 slope;
uint256 lastAmount;
uint256 lastUpdated;
}
/**********************************************************************************************/
/*** Events ***/
/**********************************************************************************************/
/**
* @dev Emitted when the rate limit data is set.
* @param key The identifier for the rate limit.
* @param maxAmount The maximum allowed amount for the rate limit.
* @param slope The slope value used in the rate limit calculation.
* @param lastAmount The amount left available at the last update.
* @param lastUpdated The timestamp when the rate limit was last updated.
*/
event RateLimitDataSet(
bytes32 indexed key,
uint256 maxAmount,
uint256 slope,
uint256 lastAmount,
uint256 lastUpdated
);
/**
* @dev Emitted when a rate limit decrease is triggered.
* @param key The identifier for the rate limit.
* @param amountToDecrease The amount to decrease from the current rate limit.
* @param oldRateLimit The previous rate limit value before triggering.
* @param newRateLimit The new rate limit value after triggering.
*/
event RateLimitDecreaseTriggered(
bytes32 indexed key,
uint256 amountToDecrease,
uint256 oldRateLimit,
uint256 newRateLimit
);
/**
* @dev Emitted when a rate limit increase is triggered.
* @param key The identifier for the rate limit.
* @param amountToIncrease The amount to increase from the current rate limit.
* @param oldRateLimit The previous rate limit value before triggering.
* @param newRateLimit The new rate limit value after triggering.
*/
event RateLimitIncreaseTriggered(
bytes32 indexed key,
uint256 amountToIncrease,
uint256 oldRateLimit,
uint256 newRateLimit
);
/**********************************************************************************************/
/*** State variables ***/
/**********************************************************************************************/
/**
* @dev Returns the controller identifier as a bytes32 value.
* @return The controller identifier.
*/
function CONTROLLER() external view returns (bytes32);
/**********************************************************************************************/
/*** Admin functions ***/
/**********************************************************************************************/
/**
* @dev Sets rate limit data for a specific key.
* @param key The identifier for the rate limit.
* @param maxAmount The maximu
Submitted on: 2025-09-25 16:58:14
Comments
Log in to comment.
No comments yet.