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": {
"contracts/l1/Depository.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Implementation, OwnerOnly, ZeroAddress} from "../Implementation.sol";
import {IToken} from "../interfaces/IToken.sol";
interface IDepositProcessor {
/// @dev Sends a message to the L2 side via a corresponding bridge.
/// @param target Staking target addresses.
/// @param amount Corresponding staking amount.
/// @param bridgePayload Bridge payload necessary (if required) for a specific bridge relayer.
/// @param operation Funds operation: stake / unstake.
/// @param sender Sender account.
function sendMessage(address target, uint256 amount, bytes memory bridgePayload, bytes32 operation, address sender)
external
payable;
}
interface IST {
/// @dev Deposits OLAS in exchange for stOLAS tokens.
/// @param assets OLAS amount.
/// @param receiver Receiver account address.
/// @return shares stOLAS amount.
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/// @dev Calculates reserve and stake balances, and top-ups stOLAS or Depository.
/// @param reserveAmount Additional reserve OLAS amount.
/// @param stakeAmount Additional stake OLAS amount.
/// @param topUp Top up amount to be sent or received.
/// @param direction To stOLAS, if true, and to Depository otherwise.
function syncStakeBalances(uint256 reserveAmount, uint256 stakeAmount, uint256 topUp, bool direction) external;
/// @dev stOLAS reserve balance.
function reserveBalance() external view returns (uint256);
}
/// @dev Zero value.
error ZeroValue();
/// @dev The contract is already initialized.
error AlreadyInitialized();
/// @dev Wrong length of arrays.
error WrongArrayLength();
/// @dev Product type deposit overflow.
/// @param productType Current product type.
/// @param depositAmount Deposit amount.
error ProductTypeDepositOverflow(uint8 productType, uint256 depositAmount);
/// @dev Value overflow.
/// @param provided Overflow value.
/// @param max Maximum possible value.
error Overflow(uint256 provided, uint256 max);
/// @dev Staking model already exists.
/// @param stakingModelId Staking model Id.
error StakingModelAlreadyExists(uint256 stakingModelId);
/// @dev Wrong staking model Id provided.
/// @param stakingModelId Staking model Id.
error WrongStakingModel(uint256 stakingModelId);
/// @dev Unauthorized account.
/// @param account Account address.
error UnauthorizedAccount(address account);
/// @dev Caught reentrancy violation.
error ReentrancyGuard();
/// @dev Contract is paused.
error Paused();
// Product type enum
enum ProductType {
Alpha,
Beta,
Final
}
// Staking model status enum
enum StakingModelStatus {
Retired,
Active,
Inactive
}
// Staking model struct
struct StakingModel {
// Max available supply
uint96 supply;
// Remaining supply
uint96 remainder;
// Stake limit per slot as the deposit amount required for a single service stake
uint96 stakeLimitPerSlot;
// Staking model status: Retired, Active, Inactive
StakingModelStatus status;
}
/// @title Depository - Smart contract for the stOLAS Depository.
contract Depository is Implementation {
event TreasuryUpdated(address indexed treasury);
event LzOracleUpdated(address indexed lzOracle);
event ProductTypeUpdated(ProductType productType);
event SetDepositProcessorChainIds(address[] depositProcessors, uint256[] chainIds);
event StakingModelActivated(
uint256 indexed chainId, address indexed stakingProxy, uint256 stakeLimitPerSlots, uint256 numSlots
);
event StakingModelStatusSet(uint256 indexed chainId, address indexed stakingProxy, StakingModelStatus status);
event Deposit(
address indexed sender,
uint256 stakeAmount,
uint256 stAmount,
uint256[] chainIds,
address[] stakingProxies,
uint256[] amounts
);
event Unstake(address indexed sender, uint256[] chainIds, address[] stakingProxies, uint256[] amounts);
event Retired(uint256[] chainIds, address[] stakingProxies);
event DepositoryPaused();
event DepositoryUnpaused();
// Depository version
string public constant VERSION = "0.1.0";
// Stake operation
bytes32 public constant STAKE = 0x1bcc0f4c3fad314e585165815f94ecca9b96690a26d6417d7876448a9a867a69;
// Unstake operation
bytes32 public constant UNSTAKE = 0x8ca9a95e41b5eece253c93f5b31eed1253aed6b145d8a6e14d913fdf8e732293;
// Unstake-retired operation
bytes32 public constant UNSTAKE_RETIRED = 0x9065ad15d9673159e4597c86084aff8052550cec93c5a6e44b3f1dba4c8731b3;
// Alpha product type deposit amount limit
uint256 public constant ALPHA_DEPOSIT_LIMIT = 1_000 ether;
// Beta product type deposit amount limit
uint256 public constant BETA_DEPOSIT_LIMIT = 10_000 ether;
// OLAS contract address
address public immutable olas;
// stOLAS contract address
address public immutable st;
// Treasury contract address
address public treasury;
// Layer Zero oracle
address public lzOracle;
// Product type value
ProductType public productType;
// Pause switcher
uint256 public paused;
// Reentrancy lock
bool transient _locked;
// Mapping for staking model Id => staking model
mapping(uint256 => StakingModel) public mapStakingModels;
// Mapping for L2 chain Id => dedicated deposit processors
mapping(uint256 => address) public mapChainIdDepositProcessors;
// Mapping for account => deposit amounts
mapping(address => uint256) public mapAccountDeposits;
// Mapping for account => withdraw amounts
mapping(address => uint256) public mapAccountWithdraws;
/// @dev Depository constructor.
/// @param _olas OLAS address.
/// @param _st stOLAS address.
constructor(address _olas, address _st) {
olas = _olas;
st = _st;
}
/// @dev Creates and activates staking model.
/// @param chainId Chain Id.
/// @param stakingProxy Corresponding staking proxy address.
/// @param stakeLimitPerSlot Corresponding staking limit per each staking slot.
/// @param numSlots Corresponding number of staking slots.
function _createAndActivateStakingModel(
uint256 chainId,
address stakingProxy,
uint256 stakeLimitPerSlot,
uint256 numSlots
) internal {
uint256 supply = stakeLimitPerSlot * numSlots;
// Check for overflow
if (supply > type(uint96).max) {
revert Overflow(supply, type(uint96).max);
}
// Check for zero value
if (supply == 0) {
revert ZeroValue();
}
// Check for zero address
if (stakingProxy == address(0)) {
revert ZeroAddress();
}
// Push a pair of key defining variables into one key: chainId | stakingProxy
// stakingProxy occupies first 160 bits, chainId occupies next bits as they both fit well in uint256
uint256 stakingModelId = uint256(uint160(stakingProxy));
stakingModelId |= chainId << 160;
// Get staking model struct
StakingModel storage stakingModel = mapStakingModels[stakingModelId];
// Check for existing staking model
if (stakingModel.supply > 0) {
revert StakingModelAlreadyExists(stakingModelId);
}
// Set supply and activate
stakingModel.supply = uint96(supply);
stakingModel.remainder = uint96(supply);
stakingModel.stakeLimitPerSlot = uint96(stakeLimitPerSlot);
stakingModel.status = StakingModelStatus.Active;
emit StakingModelActivated(chainId, stakingProxy, stakeLimitPerSlot, numSlots);
}
/// @dev Sets staking model status.
/// @param chainId Chain Id.
/// @param stakingProxy Corresponding staking proxy address.
/// @param status Corresponding staking model status.
function _setStakingModelStatus(uint256 chainId, address stakingProxy, StakingModelStatus status) internal {
// Get staking model Id
// Push a pair of key defining variables into one key: chainId | stakingProxy
// stakingProxy occupies first 160 bits, chainId occupies next bits as they both fit well in uint256
uint256 stakingModelId = uint256(uint160(stakingProxy));
stakingModelId |= chainId << 160;
// Check for staking model existence
if (mapStakingModels[stakingModelId].supply == 0) {
revert WrongStakingModel(stakingModelId);
}
// Record staking model status
mapStakingModels[stakingModelId].status = status;
emit StakingModelStatusSet(chainId, stakingProxy, status);
}
/// @dev Sends message according to the required operation: stake, unstake, etc.
/// @param chainIds Set of chain Ids with staking proxies.
/// @param stakingProxies Set of staking proxies corresponding to each chain Id.
/// @param amounts Corresponding OLAS amounts for each staking proxy.
/// @param bridgePayloads Bridge payloads corresponding to each chain Id.
/// @param values Value amounts for each bridge interaction, if applicable.
/// @param operation Operation type.
function _operationSendMessage(
uint256[] memory chainIds,
address[] memory stakingProxies,
uint256[] memory amounts,
bytes[] memory bridgePayloads,
uint256[] memory values,
bytes32 operation,
address sender
) internal {
// Traverse all array elements
for (uint256 i = 0; i < chainIds.length; ++i) {
if (amounts[i] == 0) continue;
// Get corresponding deposit processor
address depositProcessor = mapChainIdDepositProcessors[chainIds[i]];
// Check for zero address
if (depositProcessor == address(0)) {
revert ZeroAddress();
}
// Stake related only
if (operation == STAKE) {
// Transfer OLAS to depositProcessor
IToken(olas).transfer(depositProcessor, amounts[i]);
}
// Perform operation on corresponding Staker on L2
IDepositProcessor(depositProcessor).sendMessage{value: values[i]}(
stakingProxies[i], amounts[i], bridgePayloads[i], operation, sender
);
}
}
/// @dev Depository initializer.
function initialize() external {
// Check for already initialized
if (owner != address(0)) {
revert AlreadyInitialized();
}
owner = msg.sender;
productType = ProductType.Alpha;
paused = 1;
}
/// @dev Changes Treasury contract address.
/// @param newTreasury Address of a new treasury.
function changeTreasury(address newTreasury) external {
// Check for ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for zero address
if (newTreasury == address(0)) {
revert ZeroAddress();
}
treasury = newTreasury;
emit TreasuryUpdated(newTreasury);
}
/// @dev Changes Layer Zero oracle contract address.
/// @param newLzOracle Address of a new lzOracle.
function changeLzOracle(address newLzOracle) external {
// Check for ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for zero address
if (newLzOracle == address(0)) {
revert ZeroAddress();
}
lzOracle = newLzOracle;
emit LzOracleUpdated(newLzOracle);
}
/// @dev Changes product type.
/// @param newProductType New product type.
function changeProductType(ProductType newProductType) external {
// Check for ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
productType = newProductType;
emit ProductTypeUpdated(newProductType);
}
/// @dev Sets deposit processor contracts addresses and L2 chain Ids.
/// @notice It is the contract owner responsibility to set correct L1 deposit processor contracts
/// and corresponding supported L2 chain Ids.
/// @param depositProcessors Set of deposit processor contract addresses on L1.
/// @param chainIds Set of corresponding L2 chain Ids.
function setDepositProcessorChainIds(address[] memory depositProcessors, uint256[] memory chainIds) external {
// Check for the ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for array length correctness
if (depositProcessors.length == 0 || depositProcessors.length != chainIds.length) {
revert WrongArrayLength();
}
// Link L1 and L2 bridge mediators, set L2 chain Ids
for (uint256 i = 0; i < chainIds.length; ++i) {
// Check supported chain Ids on L2
if (chainIds[i] == 0) {
revert ZeroValue();
}
// Note: depositProcessors[i] might be zero if there is a need to stop processing a specific L2 chain Id
mapChainIdDepositProcessors[chainIds[i]] = depositProcessors[i];
}
emit SetDepositProcessorChainIds(depositProcessors, chainIds);
}
/// @dev Creates and activates staking models.
/// @param chainIds Chain Ids.
/// @param stakingProxies Corresponding staking proxy addresses.
/// @param stakeLimitPerSlots Corresponding staking limits per each staking slot.
/// @param numSlots Corresponding number of staking slots.
function createAndActivateStakingModels(
uint256[] memory chainIds,
address[] memory stakingProxies,
uint256[] memory stakeLimitPerSlots,
uint256[] memory numSlots
) external {
// Check for ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for array lengths
if (
chainIds.length == 0 || chainIds.length != stakingProxies.length || chainIds.length != numSlots.length
|| chainIds.length != stakeLimitPerSlots.length
) {
revert WrongArrayLength();
}
// Traverse all staking models
for (uint256 i = 0; i < chainIds.length; ++i) {
// Create and activate staking model
_createAndActivateStakingModel(chainIds[i], stakingProxies[i], stakeLimitPerSlots[i], numSlots[i]);
}
}
/// @dev Creates and activates staking model via lzRead proofs.
/// @param chainId Chain Id.
/// @param stakingProxy Corresponding staking proxy address.
/// @param stakeLimitPerSlot Corresponding staking limit per each staking slot.
/// @param numSlots Corresponding number of staking slots.
function LzCreateAndActivateStakingModel(
uint256 chainId,
address stakingProxy,
uint256 stakeLimitPerSlot,
uint256 numSlots
) external {
// Check for access
if (msg.sender != lzOracle) {
revert UnauthorizedAccount(msg.sender);
}
// Create and activate staking model
_createAndActivateStakingModel(chainId, stakingProxy, stakeLimitPerSlot, numSlots);
}
/// @dev Closes staking model via lzRead proofs.
/// @param chainId Chain Id.
/// @param stakingProxy Corresponding staking proxy address.
function LzCloseStakingModel(uint256 chainId, address stakingProxy) external {
// Check for access
if (msg.sender != lzOracle) {
revert UnauthorizedAccount(msg.sender);
}
// Retire staking model
_setStakingModelStatus(chainId, stakingProxy, StakingModelStatus.Retired);
}
/// @dev Sets existing staking model statuses.
/// @notice If the model is inactive, it does not mean that it must be unstaked right away as it might continue
/// working and accumulating rewards until fully depleted. Then it must be retired and unstaked.
/// @param chainIds Set of chain Ids with staking proxies.
/// @param stakingProxies Set of staking proxies corresponding to each chain Id.
/// @param statuses Corresponding staking model statuses.
function setStakingModelStatuses(
uint256[] memory chainIds,
address[] memory stakingProxies,
StakingModelStatus[] memory statuses
) external {
// Check for ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for array length correctness
if (chainIds.length == 0 || chainIds.length != stakingProxies.length || chainIds.length != statuses.length) {
revert WrongArrayLength();
}
// Traverse staking models and statuses
for (uint256 i = 0; i < chainIds.length; ++i) {
_setStakingModelStatus(chainIds[i], stakingProxies[i], statuses[i]);
}
}
/// @dev Calculates amounts and initiates cross-chain stake request for specified models.
/// @notice It is solely caller responsibility not to run out of gas when calling this function.
/// Subject to gas estimation as the number of array elements is proportional to gas usage increase.
/// @param stakeAmount Total incoming amount to stake.
/// @param chainIds Set of chain Ids with staking proxies.
/// @param stakingProxies Set of staking proxies corresponding to each chain Id.
/// @param bridgePayloads Bridge payloads corresponding to each chain Id.
/// @param values Value amounts for each bridge interaction, if applicable.
/// @return stAmount Amount of stOLAS minted for staking.
/// @return amounts Corresponding OLAS amounts for each staking proxy.
function deposit(
uint256 stakeAmount,
uint256[] memory chainIds,
address[] memory stakingProxies,
bytes[] memory bridgePayloads,
uint256[] memory values
) external payable returns (uint256 stAmount, uint256[] memory amounts) {
// Reentrancy guard
if (_locked) {
revert ReentrancyGuard();
}
_locked = true;
// Check if contract is paused
if (paused == 2) {
revert Paused();
}
// Check for contract product type and limits
if (
(productType == ProductType.Alpha && stakeAmount > ALPHA_DEPOSIT_LIMIT)
|| (productType == ProductType.Beta && stakeAmount > BETA_DEPOSIT_LIMIT)
) {
revert ProductTypeDepositOverflow(uint8(productType), stakeAmount);
}
// Check for overflow
if (stakeAmount > type(uint96).max) {
revert Overflow(stakeAmount, type(uint96).max);
}
// Check for array lengths
if (
chainIds.length != stakingProxies.length || chainIds.length != bridgePayloads.length
|| chainIds.length != values.length
) {
revert WrongArrayLength();
}
// Get stOLAS reserve balance for staking
uint256 stReserveBalance = IST(st).reserveBalance();
// Actual remainder is equal to stake amount plus reserve balance
// If stakeAmount is zero, stakes are performed from reserves
uint256 actualRemainder = stakeAmount + stReserveBalance;
// Check for zero value
if (actualRemainder == 0) {
revert ZeroValue();
}
uint256 actualStakeAmount;
// Allocate arrays of max possible size
amounts = new uint256[](chainIds.length);
// Collect staking contracts and amounts
for (uint256 i = 0; i < chainIds.length; ++i) {
// Push a pair of key defining variables into one key: chainId | stakingProxy
// stakingProxy occupies first 160 bits, chainId occupies next bits as they both fit well in uint256
uint256 stakingModelId = uint256(uint160(stakingProxies[i]));
stakingModelId |= chainIds[i] << 160;
StakingModel memory stakingModel = mapStakingModels[stakingModelId];
// Check for model existence and activity
if (stakingModel.supply == 0 || stakingModel.status != StakingModelStatus.Active) {
revert WrongStakingModel(stakingModelId);
}
// Skip zero available funds model
if (stakingModel.remainder == 0) {
continue;
}
// Adjust staking amount to not overflow the max allowed one
amounts[i] = actualRemainder;
// Upper limit
if (amounts[i] > stakingModel.stakeLimitPerSlot) {
amounts[i] = stakingModel.stakeLimitPerSlot;
}
// Lower limit
if (amounts[i] > stakingModel.remainder) {
amounts[i] = stakingModel.remainder;
// Update staking model remainder
mapStakingModels[stakingModelId].remainder = 0;
} else {
// Update staking model remainder
mapStakingModels[stakingModelId].remainder = stakingModel.remainder - uint96(amounts[i]);
}
// Adjust actualRemainder
actualRemainder -= amounts[i];
// Increase actual stake amount
actualStakeAmount += amounts[i];
if (actualRemainder == 0) {
break;
}
}
// Deposit provided stake amount
if (stakeAmount > 0) {
// Increase total account deposit amount
mapAccountDeposits[msg.sender] += stakeAmount;
// Calculates stAmount and mints stOLAS
stAmount = IST(st).deposit(stakeAmount, msg.sender);
// Get OLAS from sender
IToken(olas).transferFrom(msg.sender, address(this), stakeAmount);
}
uint256 topUp;
// If provided stake amount is not fully utilized for stake, approve it for transfer to stOLAS
// The following holds true: actualRemainder + actualStakeAmount = stReserveBalance + stakeAmount
// There are two cases possible
if (actualRemainder > stReserveBalance) {
// Since actualRemainder accounts for partial stakeAmount as well if it exceeds stReserveBalance,
// that partial amount from stakeAmount must be deposited to stOLAS to cover that difference
// Calculate OLAS leftovers that are not going to be staked now
topUp = actualRemainder - stReserveBalance;
IToken(olas).approve(st, topUp);
// Top up stOLAS and record correct balances including leftovers for reserve and actual stake amount
IST(st).syncStakeBalances(actualRemainder, actualStakeAmount, topUp, true);
} else if (actualStakeAmount >= stakeAmount) {
// Since actualStakeAmount accounts for partial stReserveBalance as well if it exceeds stakeAmount,
// that partial amount from stReserveBalance must be deposited to address(this) to cover that difference
// Note that actualStakeAmount == stakeAmount results in a zero topUp value, meaning OLAS is reserved
// for sending to staking contracts, however st.stakedBalance must be updated
// Calculate OLAS to be additionally deposited to address(this)
topUp = actualStakeAmount - stakeAmount;
// Pull required funds from stOLAS and record correct balances
IST(st).syncStakeBalances(actualRemainder, actualStakeAmount, topUp, false);
}
// Note it is not possible such that actualRemainder > stReserveBalance && actualStakeAmount > stakeAmount,
// as this would result in strict inequality: actualRemainder + actualStakeAmount >> stReserveBalance + stakeAmount
// Send funds to staking via relevant deposit processors
_operationSendMessage(chainIds, stakingProxies, amounts, bridgePayloads, values, STAKE, msg.sender);
emit Deposit(msg.sender, stakeAmount, stAmount, chainIds, stakingProxies, amounts);
}
/// @dev Calculates amounts and initiates cross-chain unstake request for specified models by Treasury.
/// @notice This action deducts reserves from their staked part and get them back as vault assets.
/// @notice It is solely caller responsibility not to run out of gas when calling this function.
/// Subject to gas estimation as the number of array elements is proportional to gas usage increase.
/// @param unstakeAmount Total amount to unstake.
/// @param chainIds Set of chain Ids with staking proxies.
/// @param stakingProxies Set of staking proxies corresponding to each chain Id.
/// @param bridgePayloads Bridge payloads corresponding to each chain Id.
/// @param values Value amounts for each bridge interaction, if applicable.
/// @param sender Sender account.
/// @return amounts Corresponding OLAS amounts for each staking proxy.
function unstake(
uint256 unstakeAmount,
uint256[] memory chainIds,
address[] memory stakingProxies,
bytes[] memory bridgePayloads,
uint256[] memory values,
address sender
) external payable returns (uint256[] memory amounts) {
// Reentrancy guard
if (_locked) {
revert ReentrancyGuard();
}
_locked = true;
// Check for Treasury access
if (msg.sender != treasury) {
revert UnauthorizedAccount(msg.sender);
}
// Check array lengths
if (
chainIds.length == 0 || chainIds.length != stakingProxies.length || chainIds.length != bridgePayloads.length
|| chainIds.length != values.length
) {
revert WrongArrayLength();
}
// Allocate arrays of max possible size
amounts = new uint256[](chainIds.length);
// Collect staking contracts and amounts
for (uint256 i = 0; i < chainIds.length; ++i) {
// Push a pair of key defining variables into one key: chainId | stakingProxy
// stakingProxy occupies first 160 bits, chainId occupies next bits as they both fit well in uint256
uint256 stakingModelId = uint256(uint160(stakingProxies[i]));
stakingModelId |= chainIds[i] << 160;
StakingModel memory stakingModel = mapStakingModels[stakingModelId];
// Check for model existence
if (stakingModel.supply == 0) {
revert WrongStakingModel(stakingModelId);
}
// Adjust unstaking amount to not overflow the max allowed one
amounts[i] = stakingModel.supply - stakingModel.remainder;
if (amounts[i] > stakingModel.stakeLimitPerSlot) {
amounts[i] = stakingModel.stakeLimitPerSlot;
}
if (unstakeAmount > amounts[i]) {
// Remainder resulting value is limited by stakingModel.supply
mapStakingModels[stakingModelId].remainder += uint96(amounts[i]);
unstakeAmount -= amounts[i];
} else {
amounts[i] = unstakeAmount;
mapStakingModels[stakingModelId].remainder = stakingModel.remainder + uint96(unstakeAmount);
unstakeAmount = 0;
}
if (unstakeAmount == 0) {
break;
}
}
// Check if provided staking proxies result in necessary amount of tokens
if (unstakeAmount > 0) {
revert Overflow(unstakeAmount, 0);
}
// Request unstake via relevant deposit processors
_operationSendMessage(chainIds, stakingProxies, amounts, bridgePayloads, values, UNSTAKE, sender);
emit Unstake(msg.sender, chainIds, stakingProxies, amounts);
}
/// @dev Calculates amounts and initiates cross-chain unstake request for specified retired models.
/// @notice This action deducts reserves from their staked part and get them back as assets reserved for staking.
/// @notice It is solely caller responsibility not to run out of gas when calling this function.
/// Subject to gas estimation as the number of array elements is proportional to gas usage increase.
/// @param chainIds Set of chain Ids with staking proxies.
/// @param stakingProxies Set of staking proxies corresponding to each chain Id.
/// @param bridgePayloads Bridge payloads corresponding to each chain Id.
/// @param values Value amounts for each bridge interaction, if applicable.
/// @return amounts Corresponding OLAS amounts for each staking proxy.
function unstakeRetired(
uint256[] memory chainIds,
address[] memory stakingProxies,
bytes[] memory bridgePayloads,
uint256[] memory values
) external payable returns (uint256[] memory amounts) {
// Reentrancy guard
if (_locked) {
revert ReentrancyGuard();
}
_locked = true;
// Check array lengths
if (
chainIds.length == 0 || chainIds.length != stakingProxies.length || chainIds.length != bridgePayloads.length
|| chainIds.length != values.length
) {
revert WrongArrayLength();
}
// Allocate arrays of max possible size
amounts = new uint256[](chainIds.length);
// Traverse and collect retired models amounts
for (uint256 i = 0; i < chainIds.length; ++i) {
// Push a pair of key defining variables into one key: chainId | stakingProxy
// stakingProxy occupies first 160 bits, chainId occupies next bits as they both fit well in uint256
uint256 stakingModelId = uint256(uint160(stakingProxies[i]));
stakingModelId |= chainIds[i] << 160;
StakingModel memory stakingModel = mapStakingModels[stakingModelId];
// Check for retired model status
if (stakingModel.status != StakingModelStatus.Retired) {
revert WrongStakingModel(stakingModelId);
}
// Check for model existence
if (stakingModel.supply == 0) {
revert WrongStakingModel(stakingModelId);
}
// Skip if model is already fully unstaked
if (stakingModel.remainder == stakingModel.supply) {
continue;
}
// Adjust unstaking amount to not overflow the max allowed one
amounts[i] = stakingModel.supply - stakingModel.remainder;
if (amounts[i] > stakingModel.stakeLimitPerSlot) {
amounts[i] = stakingModel.stakeLimitPerSlot;
}
// Update staking model remainder
mapStakingModels[stakingModelId].remainder += uint96(amounts[i]);
}
// Request unstake for retired models via relevant deposit processors
_operationSendMessage(chainIds, stakingProxies, amounts, bridgePayloads, values, UNSTAKE_RETIRED, msg.sender);
emit Unstake(msg.sender, chainIds, stakingProxies, amounts);
}
/// @dev Close specified retired models.
/// @notice This action is irreversible and clears up staking model info.
/// @param chainIds Set of chain Ids with staking proxies.
/// @param stakingProxies Set of staking proxies corresponding to each chain Id.
function closeRetiredStakingModels(uint256[] memory chainIds, address[] memory stakingProxies) external {
// Check array lengths
if (chainIds.length == 0 || chainIds.length != stakingProxies.length) {
revert WrongArrayLength();
}
// Traverse and delete retired models
for (uint256 i = 0; i < chainIds.length; ++i) {
// Push a pair of key defining variables into one key: chainId | stakingProxy
// stakingProxy occupies first 160 bits, chainId occupies next bits as they both fit well in uint256
uint256 stakingModelId = uint256(uint160(stakingProxies[i]));
stakingModelId |= chainIds[i] << 160;
StakingModel memory stakingModel = mapStakingModels[stakingModelId];
// Check for retired model status
if (stakingModel.status != StakingModelStatus.Retired) {
revert WrongStakingModel(stakingModelId);
}
// Check for model existence and remainder as Staking Proxy must be fully unstaked
if (stakingModel.supply == 0 || stakingModel.remainder != stakingModel.supply) {
revert WrongStakingModel(stakingModelId);
}
// Remove staking model
delete mapStakingModels[stakingModelId];
}
emit Retired(chainIds, stakingProxies);
}
/// @dev Pauses contract.
function pause() external {
// Check for ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
paused = 2;
emit DepositoryPaused();
}
/// @dev Unpauses contract.
function unpause() external {
// Check for ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
paused = 1;
emit DepositoryUnpaused();
}
/// @dev Gets staking model Id by provided chain Id and staking proxy address.
/// @param chainId Chain Id.
/// @param stakingProxy Staking proxy address.
function getStakingModelId(uint256 chainId, address stakingProxy) external pure returns (uint256) {
return uint256(uint160(stakingProxy)) | (chainId << 160);
}
/// @dev Gets chain Id and staking proxy address by provided staking model Id.
/// @param stakingModelId Staking model Id.
function getChainIdAndStakingProxy(uint256 stakingModelId) external pure returns (uint256, address) {
return ((stakingModelId >> 160), address(uint160(stakingModelId)));
}
}
"
},
"contracts/Implementation.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
/// @dev Only `owner` has a privilege, but the `sender` was provided.
/// @param sender Sender address.
/// @param owner Required sender address as an owner.
error OwnerOnly(address sender, address owner);
/// @dev Zero address.
error ZeroAddress();
/// @title Implementation - Smart contract for default minimal implementation
contract Implementation {
event OwnerUpdated(address indexed owner);
event ImplementationUpdated(address indexed implementation);
// Code position in storage is bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
bytes32 public constant PROXY_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
// Contract owner address
address public owner;
/// @dev Changes contract owner address.
/// @param newOwner Address of a new owner.
function changeOwner(address newOwner) external {
// Check for ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for zero address
if (newOwner == address(0)) {
revert ZeroAddress();
}
owner = newOwner;
emit OwnerUpdated(newOwner);
}
/// @dev Changes depository implementation contract address.
/// @param newImplementation New implementation contract address.
function changeImplementation(address newImplementation) external {
// Check for ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for zero address
if (newImplementation == address(0)) {
revert ZeroAddress();
}
// Store depository implementation address
assembly {
sstore(PROXY_SLOT, newImplementation)
}
emit ImplementationUpdated(newImplementation);
}
}
"
},
"contracts/interfaces/IToken.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
// ERC20 token interface
interface IToken {
/// @dev Transfers the token amount.
/// @param to Address to transfer to.
/// @param amount The amount to transfer.
/// @return True if the function execution is successful.
function transfer(address to, uint256 amount) external returns (bool);
/// @dev Transfers the token amount that was previously approved up until the maximum allowance.
/// @param from Account address to transfer from.
/// @param to Account address to transfer to.
/// @param amount Amount to transfer to.
/// @return True if the function execution is successful.
function transferFrom(address from, address to, uint256 amount) external returns (bool);
/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
/// @param spender Account address that will be able to transfer tokens on behalf of the caller.
/// @param amount Token amount.
/// @return True if the function execution is successful.
function approve(address spender, uint256 amount) external returns (bool);
/// @dev Mints tokens.
/// @param account Account address.
/// @param amount Token amount.
function mint(address account, uint256 amount) external;
/// @dev Burns tokens.
/// @param amount Token amount.
function burn(uint256 amount) external;
/// @dev Gets the amount of tokens owned by a specified account.
/// @param account Account address.
/// @return Amount of tokens owned.
function balanceOf(address account) external view returns (uint256);
}
// ERC721 token interface
interface INFToken {
/// @dev Sets token `id` as the allowance of `spender` over the caller's tokens.
/// @param spender Account address that will be able to transfer the token on behalf of the caller.
/// @param id Token id.
function approve(address spender, uint256 id) external;
/// @dev Transfers a specified token Id.
/// @param from Account address to transfer from.
/// @param to Account address to transfer to.
/// @param id Token id.
function transferFrom(address from, address to, uint256 id) external;
/// @dev Transfers a specified token Id with a callback.
/// @param from Account address to transfer from.
/// @param to Account address to transfer to.
/// @param id Token id.
function safeTransferFrom(address from, address to, uint256 id) external;
}
"
}
},
"settings": {
"remappings": [
"@gnosis.pm/=node_modules/@gnosis.pm/",
"@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/",
"@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"@registries/=lib/autonolas-registries/",
"@solmate/=lib/solmate/",
"autonolas-registries/=lib/autonolas-registries/",
"devtools/=lib/devtools/packages/toolbox-foundry/src/",
"ds-test/=lib/autonolas-registries/lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
"forge-std/=lib/autonolas-registries/lib/forge-std/src/",
"halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
"layerzero-v2/=lib/layerzero-v2/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"solmate/=lib/solmate/src/"
],
"optimizer": {
"enabled": true,
"runs": 1000000
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "prague",
"viaIR": true
}
}}
Submitted on: 2025-10-24 09:54:14
Comments
Log in to comment.
No comments yet.