Skip to content

Commit

Permalink
feat: blocklist
Browse files Browse the repository at this point in the history
  • Loading branch information
0xvv committed Dec 3, 2024
1 parent 4513c1e commit 5224eea
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 8 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,10 @@ sequenceDiagram
D->>U: Send principial + net rewards
```

## OFAC checking / Blocklist

If the admin sets the oracle address to a non-zero address, the contract will check the OFAC list for the address of the msg.sender when depositing and the validator owner when requesting exits and withdrawals.

Admin can also block an address by calling `ban(address, bytes)` on the contract. This will prevent the address from depositing, exiting or withdrawing. And force the exit of the validators provided if provided and the user is not sanctioned.

If a user was wrongly banned or the sanctions were lifted, the admin can call `unban(address)` to remove the address from the blocklist.
51 changes: 46 additions & 5 deletions src/contracts/StakingContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ contract StakingContract {
error MaximumOperatorCountAlreadyReached();
error LastEditAfterSnapshot();
error PublicKeyNotInContract();
error AddressSanctioned(address sanctioned);
error AddressSanctioned(address sanctionedAccount);
error AddressBlocked(address blockedAccount);

struct ValidatorAllocationCache {
bool used;
Expand Down Expand Up @@ -731,7 +732,7 @@ contract StakingContract {
if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0) {
revert InvalidPublicKeys();
}
_revertIfSanctioned(msg.sender);
_revertIfSanctionedOrBlocked(msg.sender);
for (uint256 i = 0; i < _publicKeys.length; ) {
bytes memory publicKey = BytesLib.slice(_publicKeys, i, PUBLIC_KEY_LENGTH);
bytes32 pubKeyRoot = _getPubKeyRoot(publicKey);
Expand All @@ -753,6 +754,43 @@ contract StakingContract {
StakingContractStorageLib.setDepositStopped(val);
}

/// @notice Utility to ban a user, exits the validators provided if account is not OFAC sanctioned
/// @param _account Account to ban
/// @param _publicKeys Public keys to exit
function blockAccount(address _account, bytes calldata _publicKeys) external onlyAdmin {
if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0) {
revert InvalidPublicKeys();
}
StakingContractStorageLib.getBlocklist().value[_account] = true;
address sanctionsOracle = StakingContractStorageLib.getSanctionsOracle();
if (sanctionsOracle != address(0)) {
if (ISanctionsOracle(sanctionsOracle).isSanctioned(_account)) {
return;
}
}
for (uint256 i = 0; i < _publicKeys.length; ) {
bytes memory publicKey = BytesLib.slice(_publicKeys, i, PUBLIC_KEY_LENGTH);
bytes32 pubKeyRoot = _getPubKeyRoot(publicKey);
address withdrawer = _getWithdrawer(pubKeyRoot);
if (_account != withdrawer) {
revert Unauthorized();
}
_setExitRequest(pubKeyRoot, true);
emit ExitRequest(withdrawer, publicKey);
unchecked {
i += PUBLIC_KEY_LENGTH;
}
}
}

function unblock(address _account) external onlyAdmin {
StakingContractStorageLib.getBlocklist().value[_account] = false;
}

function isBlocked(address _account) public view returns (bool) {
return StakingContractStorageLib.getBlocklist().value[_account];
}

/// ██ ███ ██ ████████ ███████ ██████ ███ ██ █████ ██
/// ██ ████ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██
/// ██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ███████ ██
Expand Down Expand Up @@ -909,7 +947,7 @@ contract StakingContract {
if (StakingContractStorageLib.getDepositStopped()) {
revert DepositsStopped();
}
_revertIfSanctioned(msg.sender);
_revertIfSanctionedOrBlocked(msg.sender);
if (msg.value == 0 || msg.value % DEPOSIT_SIZE != 0) {
revert InvalidDepositValue();
}
Expand Down Expand Up @@ -953,7 +991,7 @@ contract StakingContract {
) internal {
bytes32 publicKeyRoot = _getPubKeyRoot(_publicKey);
address withdrawer = _getWithdrawer(publicKeyRoot);
_revertIfSanctioned(withdrawer);
_revertIfSanctionedOrBlocked(withdrawer);
bytes32 feeRecipientSalt = sha256(abi.encodePacked(_prefix, publicKeyRoot));
address implementation = StakingContractStorageLib.getFeeRecipientImplementation();
address feeRecipientAddress = Clones.predictDeterministicAddress(implementation, feeRecipientSalt);
Expand All @@ -970,12 +1008,15 @@ contract StakingContract {
}
}

function _revertIfSanctioned(address account) internal {
function _revertIfSanctionedOrBlocked(address account) internal {
address sanctionsOracle = StakingContractStorageLib.getSanctionsOracle();
if (sanctionsOracle != address(0)) {
if (ISanctionsOracle(sanctionsOracle).isSanctioned(account)) {
revert AddressSanctioned(account);
}
}
if (StakingContractStorageLib.getBlocklist().value[account]) {
revert AddressBlocked(account);
}
}
}
2 changes: 1 addition & 1 deletion src/contracts/interfaces/ISanctionsOracle.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pragma solidity >=0.8.10;

interface ISanctionsOracle {
function isSanctioned(address account) external returns (bool);
function isSanctioned(address account) external returns (bool);
}
20 changes: 18 additions & 2 deletions src/contracts/libs/StakingContractStorageLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,8 @@ library StakingContractStorageLib {
===========================================
=========================================*/

bytes32 internal constant SANCTIONS_ORACLE_SLOT = bytes32(uint256(keccak256("StakingContract.sanctionsOracle")) - 1);
bytes32 internal constant SANCTIONS_ORACLE_SLOT =
bytes32(uint256(keccak256("StakingContract.sanctionsOracle")) - 1);

function getSanctionsOracle() internal view returns (address) {
return getAddress(SANCTIONS_ORACLE_SLOT);
Expand All @@ -434,5 +435,20 @@ library StakingContractStorageLib {
setAddress(SANCTIONS_ORACLE_SLOT, val);
}


/* ========================================
===========================================
=========================================*/

bytes32 internal constant BLOCKLIST_SLOT = bytes32(uint256(keccak256("StakingContract.blocklist")) - 1);

struct BlockListMap {
mapping(address => bool) value;
}

function getBlocklist() internal pure returns (BlockListMap storage p) {
bytes32 slot = BLOCKLIST_SLOT;
assembly {
p.slot := slot
}
}
}

0 comments on commit 5224eea

Please sign in to comment.