diff --git a/src/assets/AssetDelegator.sol b/src/assets/AssetDelegator.sol index cb53764..11a17cc 100644 --- a/src/assets/AssetDelegator.sol +++ b/src/assets/AssetDelegator.sol @@ -33,45 +33,46 @@ abstract contract AssetDelegator { /// @param _operator The operator (if applicable) for the operation /// @param _operation The type of operation performed /// @param _amount The amount involved in the operation - event OperationExecuted(address indexed _asset, bytes32 indexed _operator, Operation indexed _operation, uint256 _amount); + event OperationExecuted(uint128 indexed _asset, bytes32 indexed _operator, Operation indexed _operation, uint256 _amount); /// @notice Execute a delegation operation /// @param _operator The operator address (if applicable) - /// @param _asset The asset to operate on + /// @param _assetId The asset to operate on /// @param _amount The amount to operate with /// @param _operation The operation to perform /// @return success Whether the operation was successful - function op(bytes32 _operator, address _asset, uint256 _amount, Operation _operation) public virtual returns (bool) { + function op(bytes32 _operator, uint128 _assetId, uint256 _amount, Operation _operation) public virtual returns (bool) { uint8 result; - uint256 assetId = uint256(uint160(_asset)); + + // TODO: Get the asset ID from the asset address if (_operation == Operation.Deposit) { - result = DELEGATION.deposit(assetId, _amount); + result = DELEGATION.deposit(_assetId, _amount); if (result != 0) revert DelegationFailed(); } else if (_operation == Operation.Delegate) { - result = DELEGATION.delegate(_operator, assetId, _amount); + result = DELEGATION.delegate(_operator, _assetId, _amount); if (result != 0) revert DelegationFailed(); } else if (_operation == Operation.ScheduleUnstake) { - result = DELEGATION.scheduleDelegatorUnstake(_operator, assetId, _amount); + result = DELEGATION.scheduleDelegatorUnstake(_operator, _assetId, _amount); if (result != 0) revert UnstakeFailed(); } else if (_operation == Operation.CancelUnstake) { - result = DELEGATION.cancelDelegatorUnstake(_operator, assetId, _amount); + result = DELEGATION.cancelDelegatorUnstake(_operator, _assetId, _amount); if (result != 0) revert UnstakeFailed(); } else if (_operation == Operation.ExecuteUnstake) { result = DELEGATION.executeDelegatorUnstake(); if (result != 0) revert UnstakeFailed(); } else if (_operation == Operation.ScheduleWithdraw) { - result = DELEGATION.scheduleWithdraw(assetId, _amount); + result = DELEGATION.scheduleWithdraw(_assetId, _amount); if (result != 0) revert WithdrawalFailed(); } else if (_operation == Operation.CancelWithdraw) { - result = DELEGATION.cancelWithdraw(assetId, _amount); + result = DELEGATION.cancelWithdraw(_assetId, _amount); if (result != 0) revert WithdrawalFailed(); } else if (_operation == Operation.ExecuteWithdraw) { result = DELEGATION.executeWithdraw(); if (result != 0) revert WithdrawalFailed(); } - emit OperationExecuted(_asset, _operator, _operation, _amount); + emit OperationExecuted(_assetId, _operator, _operation, _amount); return true; } } diff --git a/src/assets/MasterVault.sol b/src/assets/MasterVault.sol index bb0a683..2b0bcb3 100644 --- a/src/assets/MasterVault.sol +++ b/src/assets/MasterVault.sol @@ -15,7 +15,7 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { ICrossChainBridgeManager public immutable bridgeManager; - mapping(uint32 => mapping(uint256 => address)) public syntheticAssets; + mapping(uint32 => mapping(uint256 => uint128)) public syntheticAssets; mapping(address => bool) public authorizedAdapters; mapping(bytes32 => address) public userVaults; @@ -29,7 +29,7 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { error BridgeDispatchFailed(); event SyntheticAssetCreated( - address indexed syntheticAsset, uint32 indexed originChainId, uint256 indexed originAsset, uint256 bridgeId + uint128 indexed syntheticAsset, uint32 indexed originChainId, uint256 indexed originAsset, uint256 bridgeId ); modifier onlyAuthorizedAdapter() { @@ -88,11 +88,12 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { function _handleDepositMessage(uint32 originChainId, bytes32 sender, bytes calldata payload) internal returns (bytes memory) { ICrossChainDelegatorMessage.DepositMessage memory message = CrossChainDelegatorMessage.decodeDepositMessage(payload); - address syntheticAsset = getOrCreateSyntheticAsset(originChainId, message.originAsset, message.bridgeId); + uint128 syntheticAsset = getOrCreateSyntheticAsset(originChainId, message.originAsset, message.bridgeId); address userVault = _getOrCreateUserVault(message.sender); // Mint directly to user vault - SyntheticRestakeAsset(syntheticAsset).mint(userVault, message.amount); + // TODO: Use the assets precompile to mint the userVault directly + // SyntheticRestakeAsset(syntheticAsset).mint(userVault, message.amount); UserVault(userVault).restakingDeposit(syntheticAsset, message.amount); return abi.encode(true); @@ -108,8 +109,8 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { { ICrossChainDelegatorMessage.DelegationMessage memory message = CrossChainDelegatorMessage.decodeDelegationMessage(payload); - address syntheticAsset = syntheticAssets[originChainId][message.originAsset]; - if (syntheticAsset == address(0)) revert InvalidAsset(address(0)); + uint128 syntheticAsset = syntheticAssets[originChainId][message.originAsset]; + if (syntheticAsset == uint128(0)) revert InvalidAsset(address(0)); address userVault = _getOrCreateUserVault(message.sender); UserVault(userVault).restakingDelegate(syntheticAsset, message.amount, message.operator); @@ -128,8 +129,8 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { ICrossChainDelegatorMessage.ScheduleUnstakeMessage memory message = CrossChainDelegatorMessage.decodeScheduleUnstakeMessage(payload); - address syntheticAsset = syntheticAssets[originChainId][message.originAsset]; - if (syntheticAsset == address(0)) revert InvalidAsset(address(0)); + uint128 syntheticAsset = syntheticAssets[originChainId][message.originAsset]; + if (syntheticAsset == uint128(0)) revert InvalidAsset(address(0)); address userVault = _getOrCreateUserVault(message.sender); UserVault(userVault).restakingScheduleUnstake(syntheticAsset, message.amount, message.operator); @@ -148,8 +149,8 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { ICrossChainDelegatorMessage.CancelUnstakeMessage memory message = CrossChainDelegatorMessage.decodeCancelUnstakeMessage(payload); - address syntheticAsset = syntheticAssets[originChainId][message.originAsset]; - if (syntheticAsset == address(0)) revert InvalidAsset(address(0)); + uint128 syntheticAsset = syntheticAssets[originChainId][message.originAsset]; + if (syntheticAsset == uint128(0)) revert InvalidAsset(address(0)); address userVault = _getOrCreateUserVault(message.sender); UserVault(userVault).restakingCancelUnstake(syntheticAsset, message.amount, message.operator); @@ -168,8 +169,8 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { ICrossChainDelegatorMessage.ExecuteUnstakeMessage memory message = CrossChainDelegatorMessage.decodeExecuteUnstakeMessage(payload); - address syntheticAsset = syntheticAssets[originChainId][message.originAsset]; - if (syntheticAsset == address(0)) revert InvalidAsset(address(0)); + uint128 syntheticAsset = syntheticAssets[originChainId][message.originAsset]; + if (syntheticAsset == uint128(0)) revert InvalidAsset(address(0)); address userVault = _getOrCreateUserVault(message.sender); // Just execute unstake, keep assets in vault @@ -189,8 +190,8 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { ICrossChainDelegatorMessage.ScheduleWithdrawalMessage memory message = CrossChainDelegatorMessage.decodeScheduleWithdrawalMessage(payload); - address syntheticAsset = syntheticAssets[originChainId][message.originAsset]; - if (syntheticAsset == address(0)) revert InvalidAsset(address(0)); + uint128 syntheticAsset = syntheticAssets[originChainId][message.originAsset]; + if (syntheticAsset == uint128(0)) revert InvalidAsset(address(0)); address userVault = _getOrCreateUserVault(message.sender); UserVault(userVault).restakingScheduleWithdraw(syntheticAsset, message.amount); @@ -209,8 +210,8 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { ICrossChainDelegatorMessage.CancelWithdrawalMessage memory message = CrossChainDelegatorMessage.decodeCancelWithdrawalMessage(payload); - address syntheticAsset = syntheticAssets[originChainId][message.originAsset]; - if (syntheticAsset == address(0)) revert InvalidAsset(address(0)); + uint128 syntheticAsset = syntheticAssets[originChainId][message.originAsset]; + if (syntheticAsset == uint128(0)) revert InvalidAsset(address(0)); address userVault = _getOrCreateUserVault(message.sender); UserVault(userVault).restakingCancelWithdraw(syntheticAsset, message.amount); @@ -229,8 +230,8 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { ICrossChainDelegatorMessage.ExecuteWithdrawalMessage memory message = CrossChainDelegatorMessage.decodeExecuteWithdrawalMessage(payload); - address syntheticAsset = syntheticAssets[originChainId][message.originAsset]; - if (syntheticAsset == address(0)) revert InvalidAsset(address(0)); + uint128 syntheticAsset = syntheticAssets[originChainId][message.originAsset]; + if (syntheticAsset == uint128(0)) revert InvalidAsset(address(0)); address userVault = _getOrCreateUserVault(message.sender); @@ -250,7 +251,8 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { // Only burn after successful message dispatch try bridgeManager.dispatchMessage(withdrawalMessage.encode()) { // If message dispatch succeeds, burn the synthetic asset - SyntheticRestakeAsset(syntheticAsset).burn(userVault, message.amount); + // TODO: Use the assets precompile to burn the userVault directly + // SyntheticRestakeAsset(syntheticAsset).burn(userVault, message.amount); } catch { revert BridgeDispatchFailed(); } @@ -258,20 +260,21 @@ contract MasterVault is IMasterVault, ICrossChainReceiver { return abi.encode(true); } - function getOrCreateSyntheticAsset(uint32 originChainId, uint256 originAsset, uint256 bridgeId) internal returns (address) { - address synthetic = syntheticAssets[originChainId][originAsset]; + function getOrCreateSyntheticAsset(uint32 originChainId, uint256 originAsset, uint256 bridgeId) internal returns (uint128) { + uint128 synthetic = syntheticAssets[originChainId][originAsset]; - if (synthetic == address(0)) { + if (synthetic == uint128(0)) { string memory name = string(abi.encodePacked("Synthetic Restake ", originAsset)); string memory symbol = string(abi.encodePacked("sr", originAsset)); - synthetic = address(new SyntheticRestakeAsset(name, symbol, originChainId, originAsset, bridgeId)); + address syntheticAddr = address(new SyntheticRestakeAsset(name, symbol, originChainId, originAsset, bridgeId)); + synthetic = uint128(uint160(syntheticAddr)); syntheticAssets[originChainId][originAsset] = synthetic; emit SyntheticAssetCreated(synthetic, originChainId, originAsset, bridgeId); } - return synthetic; + return uint128(uint160(synthetic)); } function authorizeAdapter(address adapter) external { diff --git a/src/assets/RemoteRestakeVault.sol b/src/assets/RemoteRestakeVault.sol index d21dcc1..2a6d59c 100644 --- a/src/assets/RemoteRestakeVault.sol +++ b/src/assets/RemoteRestakeVault.sol @@ -6,22 +6,34 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { IRemoteChainBridgeManager } from "../interfaces/IRemoteChainBridgeManager.sol"; import { ICrossChainDelegatorMessage } from "../interfaces/ICrossChainDelegatorMessage.sol"; import { CrossChainDelegatorMessage } from "../libs/CrossChainDelegatorMessage.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; contract RemoteRestakeVault { using SafeERC20 for IERC20; using CrossChainDelegatorMessage for *; + using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableSet for EnumerableSet.Bytes32Set; IRemoteChainBridgeManager public immutable bridgeManager; - // Track deposits per user per token + // Core state tracking mapping(address => mapping(address => uint256)) public userDeposits; - // Track delegations per user per token per operator mapping(address => mapping(address => mapping(bytes32 => uint256))) public userDelegations; - // Track unstaking amounts per user per token per operator mapping(address => mapping(address => mapping(bytes32 => uint256))) public userUnstaking; - // Track withdrawal amounts per user per token mapping(address => mapping(address => uint256)) public userWithdrawals; + // Enumerable sets for efficient iteration + EnumerableSet.AddressSet private knownTokens; + EnumerableSet.AddressSet private knownDelegators; + EnumerableSet.Bytes32Set private knownOperators; + + // Operator tracking for efficient slashing + mapping(bytes32 => mapping(address => bool)) public operatorTokens; + mapping(bytes32 => mapping(address => mapping(address => bool))) public operatorDelegators; + + // Trusted receivers for cross-chain messages + mapping(address => bool) public trustedReceivers; + error InvalidBridgeManager(); error InvalidAmount(); error InvalidToken(); @@ -32,6 +44,7 @@ contract RemoteRestakeVault { error BridgeDispatchFailed(); error InvalidRecipient(); error InvalidOperator(); + error UnauthorizedReceiver(); event AssetDeposited(address indexed token, address indexed sender, uint256 amount); event DelegationUpdated(address indexed token, address indexed sender, uint256 amount, bytes32 indexed operator); @@ -41,20 +54,71 @@ contract RemoteRestakeVault { event WithdrawalScheduled(address indexed token, address indexed sender, uint256 amount); event WithdrawalCancelled(address indexed token, address indexed sender, uint256 amount); event WithdrawalExecuted(address indexed token, address indexed sender, address indexed recipient, uint256 amount); - event ClaimRecipientSet(address indexed token, address indexed sender, address indexed recipient); + event TokensSlashed(address indexed token, bytes32 indexed operator, address indexed delegator, uint256 amount); + event TrustedReceiverUpdated(address indexed receiver, bool trusted); + + modifier validToken(address token) { + if (token == address(0)) revert InvalidToken(); + _; + } + + modifier validAmount(uint256 amount) { + if (amount == 0) revert InvalidAmount(); + _; + } + + modifier validOperator(bytes32 operator) { + if (operator == bytes32(0)) revert InvalidOperator(); + _; + } + + modifier validRecipient(address recipient) { + if (recipient == address(0)) revert InvalidRecipient(); + _; + } + + modifier onlyTrustedReceiver() { + if (!trustedReceivers[msg.sender]) revert UnauthorizedReceiver(); + _; + } + + modifier sufficientBalance(address token, uint256 amount) { + if (userDeposits[msg.sender][token] < amount) revert InsufficientBalance(); + _; + } + + modifier sufficientDelegation(address token, bytes32 operator, uint256 amount) { + if (userDelegations[msg.sender][token][operator] < amount) revert InsufficientDelegation(); + _; + } + + modifier sufficientUnstaking(address token, bytes32 operator, uint256 amount) { + if (userUnstaking[msg.sender][token][operator] < amount) revert InsufficientUnstaking(); + _; + } constructor(address _bridgeManager) { if (_bridgeManager == address(0)) revert InvalidBridgeManager(); bridgeManager = IRemoteChainBridgeManager(_bridgeManager); } - function deposit(address token, uint256 amount, uint256 bridgeId) external payable { - if (token == address(0)) revert InvalidToken(); - if (amount == 0) revert InvalidAmount(); - + function deposit( + address token, + uint256 amount, + uint256 bridgeId + ) + external + payable + validToken(token) + validAmount(amount) + { IERC20(token).safeTransferFrom(msg.sender, address(this), amount); userDeposits[msg.sender][token] += amount; + // Track known tokens and delegators + knownTokens.add(token); + knownDelegators.add(msg.sender); + ICrossChainDelegatorMessage.DepositMessage memory message = ICrossChainDelegatorMessage.DepositMessage({ bridgeId: bridgeId, originAsset: uint256(uint160(token)), @@ -65,16 +129,27 @@ contract RemoteRestakeVault { _dispatchMessage(bridgeId, message.encode()); emit AssetDeposited(token, msg.sender, amount); } - - function delegate(address token, uint256 amount, uint256 bridgeId, bytes32 operator) external payable { - if (token == address(0)) revert InvalidToken(); - if (amount == 0) revert InvalidAmount(); - if (operator == bytes32(0)) revert InvalidOperator(); - if (userDeposits[msg.sender][token] < amount) revert InsufficientBalance(); - + function delegate( + address token, + uint256 amount, + uint256 bridgeId, + bytes32 operator + ) + external + payable + validToken(token) + validAmount(amount) + validOperator(operator) + sufficientBalance(token, amount) + { userDeposits[msg.sender][token] -= amount; userDelegations[msg.sender][token][operator] += amount; + // Update tracking sets + knownOperators.add(operator); + operatorTokens[operator][token] = true; + operatorDelegators[operator][token][msg.sender] = true; + ICrossChainDelegatorMessage.DelegationMessage memory message = ICrossChainDelegatorMessage.DelegationMessage({ bridgeId: bridgeId, originAsset: uint256(uint160(token)), @@ -87,12 +162,20 @@ contract RemoteRestakeVault { emit DelegationUpdated(token, msg.sender, amount, operator); } - function scheduleUnstake(address token, uint256 amount, uint256 bridgeId, bytes32 operator) external payable { - if (token == address(0)) revert InvalidToken(); - if (amount == 0) revert InvalidAmount(); - if (operator == bytes32(0)) revert InvalidOperator(); - if (userDelegations[msg.sender][token][operator] < amount) revert InsufficientDelegation(); - + function scheduleUnstake( + address token, + uint256 amount, + uint256 bridgeId, + bytes32 operator + ) + external + payable + validToken(token) + validAmount(amount) + validOperator(operator) + sufficientDelegation(token, operator, amount) + { + userDelegations[msg.sender][token][operator] -= amount; userUnstaking[msg.sender][token][operator] += amount; ICrossChainDelegatorMessage.ScheduleUnstakeMessage memory message = ICrossChainDelegatorMessage.ScheduleUnstakeMessage({ @@ -107,13 +190,21 @@ contract RemoteRestakeVault { emit UnstakeScheduled(token, msg.sender, amount, operator); } - function cancelUnstake(address token, uint256 amount, uint256 bridgeId, bytes32 operator) external payable { - if (token == address(0)) revert InvalidToken(); - if (amount == 0) revert InvalidAmount(); - if (operator == bytes32(0)) revert InvalidOperator(); - if (userUnstaking[msg.sender][token][operator] < amount) revert InsufficientUnstaking(); - + function cancelUnstake( + address token, + uint256 amount, + uint256 bridgeId, + bytes32 operator + ) + external + payable + validToken(token) + validAmount(amount) + validOperator(operator) + sufficientUnstaking(token, operator, amount) + { userUnstaking[msg.sender][token][operator] -= amount; + userDelegations[msg.sender][token][operator] += amount; ICrossChainDelegatorMessage.CancelUnstakeMessage memory message = ICrossChainDelegatorMessage.CancelUnstakeMessage({ bridgeId: bridgeId, @@ -127,14 +218,28 @@ contract RemoteRestakeVault { emit UnstakeCancelled(token, msg.sender, amount, operator); } - function executeUnstake(address token, uint256 amount, uint256 bridgeId, bytes32 operator) external payable { - if (token == address(0)) revert InvalidToken(); - if (amount == 0) revert InvalidAmount(); - if (operator == bytes32(0)) revert InvalidOperator(); - if (userUnstaking[msg.sender][token][operator] < amount) revert InsufficientUnstaking(); - + function executeUnstake( + address token, + uint256 amount, + uint256 bridgeId, + bytes32 operator + ) + external + payable + validToken(token) + validAmount(amount) + validOperator(operator) + sufficientUnstaking(token, operator, amount) + { userUnstaking[msg.sender][token][operator] -= amount; - userDelegations[msg.sender][token][operator] -= amount; + userDeposits[msg.sender][token] += amount; + + // Clean up operator tracking if no more delegations + if (userDelegations[msg.sender][token][operator] == 0 && + userUnstaking[msg.sender][token][operator] == 0) { + operatorDelegators[operator][token][msg.sender] = false; + _cleanupOperatorIfEmpty(operator, token); + } ICrossChainDelegatorMessage.ExecuteUnstakeMessage memory message = ICrossChainDelegatorMessage.ExecuteUnstakeMessage({ bridgeId: bridgeId, @@ -148,65 +253,83 @@ contract RemoteRestakeVault { emit UnstakeExecuted(token, msg.sender, amount, operator); } - function scheduleWithdrawal(address token, uint256 amount, uint256 bridgeId) external payable { - if (token == address(0)) revert InvalidToken(); - if (amount == 0) revert InvalidAmount(); - if (userDeposits[msg.sender][token] < amount) revert InsufficientBalance(); - - userWithdrawals[msg.sender][token] += amount; - - ICrossChainDelegatorMessage.ScheduleWithdrawalMessage memory message = ICrossChainDelegatorMessage.ScheduleWithdrawalMessage({ - bridgeId: bridgeId, - originAsset: uint256(uint160(token)), - amount: amount, - sender: bytes32(uint256(uint160(msg.sender))) - }); - - _dispatchMessage(bridgeId, message.encode()); - emit WithdrawalScheduled(token, msg.sender, amount); + function handleSlashMessage( + bytes32 operator, + uint8 slashPercent + ) + internal + validOperator(operator) + returns (bool) + { + uint256 length = knownTokens.length(); + for (uint256 i = 0; i < length;) { + address token = knownTokens.at(i); + if (operatorTokens[operator][token]) { + _handleSlashForToken(operator, token, slashPercent); + } + unchecked { ++i; } + } + return true; } - function cancelWithdrawal(address token, uint256 amount, uint256 bridgeId) external payable { - if (token == address(0)) revert InvalidToken(); - if (amount == 0) revert InvalidAmount(); - if (userWithdrawals[msg.sender][token] < amount) revert InsufficientWithdrawal(); - - userWithdrawals[msg.sender][token] -= amount; - - ICrossChainDelegatorMessage.CancelWithdrawalMessage memory message = ICrossChainDelegatorMessage.CancelWithdrawalMessage({ - bridgeId: bridgeId, - originAsset: uint256(uint160(token)), - amount: amount, - sender: bytes32(uint256(uint160(msg.sender))) - }); - - _dispatchMessage(bridgeId, message.encode()); - emit WithdrawalCancelled(token, msg.sender, amount); + function _handleSlashForToken( + bytes32 operator, + address token, + uint8 slashPercent + ) + internal + { + uint256 length = knownDelegators.length(); + for (uint256 i = 0; i < length;) { + address delegator = knownDelegators.at(i); + if (operatorDelegators[operator][token][delegator]) { + uint256 delegatedAmount = userDelegations[delegator][token][operator]; + if (delegatedAmount > 0) { + uint256 slashAmount = (delegatedAmount * slashPercent) / 100; + if (slashAmount > 0) { + userDelegations[delegator][token][operator] -= slashAmount; + IERC20(token).safeTransfer(address(0), slashAmount); + emit TokensSlashed(token, operator, delegator, slashAmount); + } + } + } + unchecked { ++i; } + } } - function executeWithdrawal(address token, uint256 amount, address recipient, uint256 bridgeId) external payable { - if (token == address(0)) revert InvalidToken(); - if (amount == 0) revert InvalidAmount(); - if (recipient == address(0)) revert InvalidRecipient(); - if (userWithdrawals[msg.sender][token] < amount) revert InsufficientWithdrawal(); - - userWithdrawals[msg.sender][token] -= amount; - IERC20(token).safeTransfer(recipient, amount); - - ICrossChainDelegatorMessage.ExecuteWithdrawalMessage memory message = ICrossChainDelegatorMessage.ExecuteWithdrawalMessage({ - bridgeId: bridgeId, - originAsset: uint256(uint160(token)), - amount: amount, - sender: bytes32(uint256(uint160(msg.sender))), - recipient: bytes32(uint256(uint160(recipient))) - }); - - _dispatchMessage(bridgeId, message.encode()); - emit WithdrawalExecuted(token, msg.sender, recipient, amount); + function _cleanupOperatorIfEmpty(bytes32 operator, address token) internal { + uint256 length = knownDelegators.length(); + bool hasActiveDelegators = false; + + for (uint256 i = 0; i < length && !hasActiveDelegators;) { + address delegator = knownDelegators.at(i); + if (operatorDelegators[operator][token][delegator]) { + hasActiveDelegators = true; + } + unchecked { ++i; } + } + + if (!hasActiveDelegators) { + operatorTokens[operator][token] = false; + _cleanupOperatorIfNoTokens(operator); + } } - function getRequiredFee(uint256 bridgeId, bytes calldata message) external view returns (uint256) { - return bridgeManager.getMessageFee(bridgeId, message); + function _cleanupOperatorIfNoTokens(bytes32 operator) internal { + uint256 length = knownTokens.length(); + bool hasActiveTokens = false; + + for (uint256 i = 0; i < length && !hasActiveTokens;) { + address token = knownTokens.at(i); + if (operatorTokens[operator][token]) { + hasActiveTokens = true; + } + unchecked { ++i; } + } + + if (!hasActiveTokens) { + knownOperators.remove(operator); + } } function _dispatchMessage(uint256 bridgeId, bytes memory message) internal { @@ -218,4 +341,53 @@ contract RemoteRestakeVault { revert BridgeDispatchFailed(); } } + + // View functions + function getOperatorTokens(bytes32 operator) external view returns (address[] memory) { + uint256 length = knownTokens.length(); + uint256 count; + address[] memory tokens = new address[](length); + + for (uint256 i = 0; i < length;) { + address token = knownTokens.at(i); + if (operatorTokens[operator][token]) { + tokens[count] = token; + unchecked { ++count; } + } + unchecked { ++i; } + } + + assembly { + mstore(tokens, count) + } + + return tokens; + } + + function getOperatorDelegators(bytes32 operator, address token) external view returns (address[] memory) { + uint256 length = knownDelegators.length(); + uint256 count; + address[] memory delegators = new address[](length); + + for (uint256 i = 0; i < length;) { + address delegator = knownDelegators.at(i); + if (operatorDelegators[operator][token][delegator]) { + delegators[count] = delegator; + unchecked { ++count; } + } + unchecked { ++i; } + } + + assembly { + mstore(delegators, count) + } + + return delegators; + } + + // Admin functions + function setTrustedReceiver(address receiver, bool trusted) external validRecipient(receiver) { + trustedReceivers[receiver] = trusted; + emit TrustedReceiverUpdated(receiver, trusted); + } } diff --git a/src/assets/UserVault.sol b/src/assets/UserVault.sol index 7142708..ebe1ac9 100644 --- a/src/assets/UserVault.sol +++ b/src/assets/UserVault.sol @@ -20,59 +20,35 @@ contract UserVault is AssetDelegator { masterVault = _masterVault; } - function restakingDeposit(address syntheticAsset, uint256 amount) external onlyMasterVault returns (bool) { - return op(bytes32(0), syntheticAsset, amount, Operation.Deposit); + function restakingDeposit(uint128 assetId, uint256 amount) external onlyMasterVault returns (bool) { + return op(bytes32(0), assetId, amount, Operation.Deposit); } - function restakingDelegate(address syntheticAsset, uint256 amount, bytes32 operator) external onlyMasterVault returns (bool) { - return op(operator, syntheticAsset, amount, Operation.Delegate); + function restakingDelegate(uint128 assetId, uint256 amount, bytes32 operator) external onlyMasterVault returns (bool) { + return op(operator, assetId, amount, Operation.Delegate); } - function restakingScheduleUnstake( - address syntheticAsset, - uint256 amount, - bytes32 operator - ) - external - onlyMasterVault - returns (bool) - { - return op(operator, syntheticAsset, amount, Operation.ScheduleUnstake); + function restakingScheduleUnstake(uint128 assetId, uint256 amount, bytes32 operator) external onlyMasterVault returns (bool) { + return op(operator, assetId, amount, Operation.ScheduleUnstake); } - function restakingCancelUnstake( - address syntheticAsset, - uint256 amount, - bytes32 operator - ) - external - onlyMasterVault - returns (bool) - { - return op(operator, syntheticAsset, amount, Operation.CancelUnstake); + function restakingCancelUnstake(uint128 assetId, uint256 amount, bytes32 operator) external onlyMasterVault returns (bool) { + return op(operator, assetId, amount, Operation.CancelUnstake); } - function restakingExecuteUnstake( - address syntheticAsset, - uint256 amount, - bytes32 operator - ) - external - onlyMasterVault - returns (bool) - { - return op(operator, syntheticAsset, amount, Operation.ExecuteUnstake); + function restakingExecuteUnstake(uint128 assetId, uint256 amount, bytes32 operator) external onlyMasterVault returns (bool) { + return op(operator, assetId, amount, Operation.ExecuteUnstake); } - function restakingScheduleWithdraw(address syntheticAsset, uint256 amount) external onlyMasterVault returns (bool) { - return op(bytes32(0), syntheticAsset, amount, Operation.ScheduleWithdraw); + function restakingScheduleWithdraw(uint128 assetId, uint256 amount) external onlyMasterVault returns (bool) { + return op(bytes32(0), assetId, amount, Operation.ScheduleWithdraw); } - function restakingCancelWithdraw(address syntheticAsset, uint256 amount) external onlyMasterVault returns (bool) { - return op(bytes32(0), syntheticAsset, amount, Operation.CancelWithdraw); + function restakingCancelWithdraw(uint128 assetId, uint256 amount) external onlyMasterVault returns (bool) { + return op(bytes32(0), assetId, amount, Operation.CancelWithdraw); } - function restakingExecuteWithdraw(address syntheticAsset, uint256 amount) external onlyMasterVault returns (bool) { - return op(bytes32(0), syntheticAsset, amount, Operation.ExecuteWithdraw); + function restakingExecuteWithdraw(uint128 assetId, uint256 amount) external onlyMasterVault returns (bool) { + return op(bytes32(0), assetId, amount, Operation.ExecuteWithdraw); } }