From f22c95a96d098bafcfbce3191d6d446b1709daf9 Mon Sep 17 00:00:00 2001 From: Florian de Miramon Date: Tue, 18 Feb 2025 09:53:06 +0100 Subject: [PATCH 1/3] Add whitelisting base files --- contracts/WhiteListing/IKeyringChecker.sol | 15 +++++++++++ .../mocks/MockedKeyringChecker.sol | 27 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 contracts/WhiteListing/IKeyringChecker.sol create mode 100644 contracts/WhiteListing/mocks/MockedKeyringChecker.sol diff --git a/contracts/WhiteListing/IKeyringChecker.sol b/contracts/WhiteListing/IKeyringChecker.sol new file mode 100644 index 000000000..68a0835f9 --- /dev/null +++ b/contracts/WhiteListing/IKeyringChecker.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.19; + +/// @title Keyring Checker Interface +/// @notice Interface for a contract that checks credentials against a Keyring contract. +interface IKeyringChecker { + /** + * @notice Checks if an entity has a valid credential and supports legacy interface. + * @param policyId The ID of the policy. + * @param entity The address of the entity to check. + * @return True if the entity has a valid credential, false otherwise. + */ + function checkCredential(uint256 policyId, address entity) external view returns (bool); +} diff --git a/contracts/WhiteListing/mocks/MockedKeyringChecker.sol b/contracts/WhiteListing/mocks/MockedKeyringChecker.sol new file mode 100644 index 000000000..465a71c38 --- /dev/null +++ b/contracts/WhiteListing/mocks/MockedKeyringChecker.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity =0.8.19; + +import { IKeyringChecker } from "../interfaces/IKeyringChecker.sol"; + +/// @title Mocked Keyring Checker +/// @notice A mock implementation of IKeyringChecker for testing purposes +contract MockedKeyringChecker is IKeyringChecker { + /// @notice Whether to allow the user or not. + bool public allow = false; + + /// @notice Sets the allow flag. + /// @param _allow Whether to allow the user or not. + function setAllow(bool _allow) external { + allow = _allow; + } + + /// @notice Checks if the user satisfies the policy. + /// @param policyId The policy ID to check against (unused in mock). + /// @param entity The address to check. + /// @return result True if the user satisfies the policy, false otherwise. + /* solhint-disable-next-line no-unused-vars */ + function checkCredential(uint256 policyId, address entity) external view returns (bool) { + return allow; + } +} From c19405007d37a0b6e3d775d2c6ce90f4c6ac4218 Mon Sep 17 00:00:00 2001 From: Florian de Miramon Date: Tue, 18 Feb 2025 11:23:45 +0100 Subject: [PATCH 2/3] Implement Keyring cred check --- .../Diamond/facets/PolicyFacet.sol | 68 ++++++++++++++++++- .../IKeyringChecker.sol | 0 .../mocks/MockedKeyringChecker.sol | 2 +- 3 files changed, 68 insertions(+), 2 deletions(-) rename contracts/{WhiteListing => KeyringChecker}/IKeyringChecker.sol (100%) rename contracts/{WhiteListing => KeyringChecker}/mocks/MockedKeyringChecker.sol (92%) diff --git a/contracts/Comptroller/Diamond/facets/PolicyFacet.sol b/contracts/Comptroller/Diamond/facets/PolicyFacet.sol index ff16ff1a1..3fb9a850f 100644 --- a/contracts/Comptroller/Diamond/facets/PolicyFacet.sol +++ b/contracts/Comptroller/Diamond/facets/PolicyFacet.sol @@ -4,7 +4,7 @@ pragma solidity 0.5.16; import { VToken } from "../../../Tokens/VTokens/VToken.sol"; import { IPolicyFacet } from "../interfaces/IPolicyFacet.sol"; - +import { IKeyringChecker } from "../../../KeyringChecker/IKeyringChecker.sol"; import { XVSRewardsHelper } from "./XVSRewardsHelper.sol"; /** @@ -14,12 +14,31 @@ import { XVSRewardsHelper } from "./XVSRewardsHelper.sol"; * @notice This facet contract contains all the external pre-hook functions related to vToken */ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { + /// @notice The address of the Keyring contract. + /// @dev This could have been immutable, but not possible in upgradeable contracts. + /// @dev Consumes one storage slot. + IKeyringChecker public keyringChecker; + + /// @notice The policyId to use with the Keyring contract. + /// @dev This could have been immutable, but not possible in upgradeable contracts. + /// @dev Consumes one storage slot. + uint32 public keyringPolicyId; + + /// @notice Whether the keyring guard is enabled. + bool public keyringGuardEnabled; + /// @notice Emitted when a new borrow-side XVS speed is calculated for a market event VenusBorrowSpeedUpdated(VToken indexed vToken, uint256 newSpeed); /// @notice Emitted when a new supply-side XVS speed is calculated for a market event VenusSupplySpeedUpdated(VToken indexed vToken, uint256 newSpeed); + /// @notice Emitted when the keyring configuration is updated. + event KeyringConfigurationUpdated(address keyringChecker, uint32 keyringPolicyId); + + /// @notice Emitted when the keyring guard is enabled. + event KeyringGuardEnabled(bool enabled); + /** * @notice Checks if the account should be allowed to mint tokens in the given market * @param vToken The market to verify the mint against @@ -28,6 +47,8 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { * @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) */ function mintAllowed(address vToken, address minter, uint256 mintAmount) external returns (uint256) { + ensureUserAllowed(minter); + // Pausing is a very serious situation - we revert to sound the alarms checkProtocolPauseState(); checkActionPauseState(vToken, Action.MINT); @@ -57,6 +78,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { */ // solhint-disable-next-line no-unused-vars function mintVerify(address vToken, address minter, uint256 actualMintAmount, uint256 mintTokens) external { + ensureUserAllowed(minter); if (address(prime) != address(0)) { prime.accrueInterestAndUpdateScore(minter, vToken); } @@ -70,6 +92,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { * @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) */ function redeemAllowed(address vToken, address redeemer, uint256 redeemTokens) external returns (uint256) { + ensureUserAllowed(redeemer); checkProtocolPauseState(); checkActionPauseState(vToken, Action.REDEEM); @@ -93,6 +116,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { * @param redeemTokens The number of tokens being redeemed */ function redeemVerify(address vToken, address redeemer, uint256 redeemAmount, uint256 redeemTokens) external { + ensureUserAllowed(redeemer); require(redeemTokens != 0 || redeemAmount == 0, "redeemTokens zero"); if (address(prime) != address(0)) { prime.accrueInterestAndUpdateScore(redeemer, vToken); @@ -107,6 +131,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { * @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) */ function borrowAllowed(address vToken, address borrower, uint256 borrowAmount) external returns (uint256) { + ensureUserAllowed(borrower); // Pausing is a very serious situation - we revert to sound the alarms checkProtocolPauseState(); checkActionPauseState(vToken, Action.BORROW); @@ -162,6 +187,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { */ // solhint-disable-next-line no-unused-vars function borrowVerify(address vToken, address borrower, uint256 borrowAmount) external { + ensureUserAllowed(borrower); if (address(prime) != address(0)) { prime.accrueInterestAndUpdateScore(borrower, vToken); } @@ -181,6 +207,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { address borrower, uint256 repayAmount // solhint-disable-line no-unused-vars ) external returns (uint256) { + ensureUserAllowed(payer); checkProtocolPauseState(); checkActionPauseState(vToken, Action.REPAY); ensureListed(markets[vToken]); @@ -207,6 +234,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { uint256 actualRepayAmount, // solhint-disable-line no-unused-vars uint256 borrowerIndex // solhint-disable-line no-unused-vars ) external { + ensureUserAllowed(payer); if (address(prime) != address(0)) { prime.accrueInterestAndUpdateScore(borrower, vToken); } @@ -227,6 +255,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { address borrower, uint256 repayAmount ) external view returns (uint256) { + ensureUserAllowed(liquidator); checkProtocolPauseState(); // if we want to pause liquidating to vTokenCollateral, we should pause seizing @@ -288,6 +317,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { uint256 actualRepayAmount, // solhint-disable-line no-unused-vars uint256 seizeTokens // solhint-disable-line no-unused-vars ) external { + ensureUserAllowed(liquidator); if (address(prime) != address(0)) { prime.accrueInterestAndUpdateScore(borrower, vTokenBorrowed); prime.accrueInterestAndUpdateScore(liquidator, vTokenBorrowed); @@ -309,6 +339,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { address borrower, uint256 seizeTokens // solhint-disable-line no-unused-vars ) external returns (uint256) { + ensureUserAllowed(liquidator); // Pausing is a very serious situation - we revert to sound the alarms checkProtocolPauseState(); checkActionPauseState(vTokenCollateral, Action.SEIZE); @@ -353,6 +384,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { address borrower, uint256 seizeTokens // solhint-disable-line no-unused-vars ) external { + ensureUserAllowed(liquidator); if (address(prime) != address(0)) { prime.accrueInterestAndUpdateScore(borrower, vTokenCollateral); prime.accrueInterestAndUpdateScore(liquidator, vTokenCollateral); @@ -373,6 +405,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { address dst, uint256 transferTokens ) external returns (uint256) { + ensureUserAllowed(src); // Pausing is a very serious situation - we revert to sound the alarms checkProtocolPauseState(); checkActionPauseState(vToken, Action.TRANSFER); @@ -401,6 +434,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { */ // solhint-disable-next-line no-unused-vars function transferVerify(address vToken, address src, address dst, uint256 transferTokens) external { + ensureUserAllowed(src); if (address(prime) != address(0)) { prime.accrueInterestAndUpdateScore(src, vToken); prime.accrueInterestAndUpdateScore(dst, vToken); @@ -499,4 +533,36 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { emit VenusBorrowSpeedUpdated(vToken, borrowSpeed); } } + /// @notice Sets the keyring configuration. + /// @param _keyringChecker The address of the keyring checker. + /// @param _keyringPolicyId The policy id of the keyring. + function setKeyringConfiguration(address _keyringChecker, uint32 _keyringPolicyId) public { + ensureAdmin(msg.sender); + if (address(_keyringChecker) == address(0) || _keyringPolicyId == 0) { + revert InvalidKeyringConfiguration(address(_keyringChecker), _keyringPolicyId); + } + keyringChecker = IKeyringChecker(_keyringChecker); + keyringPolicyId = _keyringPolicyId; + emit KeyringConfigurationUpdated(address(_keyringChecker), _keyringPolicyId); + } + + /// @notice Enables or disables the keyring guard. + /// @param _enabled Whether to enable or disable the keyring guard. + function enableKeyringGuard(bool _enabled) public { + ensureAdmin(msg.sender); + keyringGuardEnabled = _enabled; + emit KeyringGuardEnabled(_enabled); + } + + /// @notice Checks if the user satisfies the policy. + /// @param _entity The address of the entity to check. + /// @return result True if the user satisfies the policy, false otherwise. + function isUserAllowed(address _entity) public view returns (bool) { + return !keyringGuardEnabled || keyringChecker.checkCredential(keyringPolicyId, _entity); + } + + /// @notice Ensures that the user satisfies the policy. + function ensureUserAllowed(address _entity) public view { + require(isUserAllowed(_entity), "User does not have Keyring credential"); + } } diff --git a/contracts/WhiteListing/IKeyringChecker.sol b/contracts/KeyringChecker/IKeyringChecker.sol similarity index 100% rename from contracts/WhiteListing/IKeyringChecker.sol rename to contracts/KeyringChecker/IKeyringChecker.sol diff --git a/contracts/WhiteListing/mocks/MockedKeyringChecker.sol b/contracts/KeyringChecker/mocks/MockedKeyringChecker.sol similarity index 92% rename from contracts/WhiteListing/mocks/MockedKeyringChecker.sol rename to contracts/KeyringChecker/mocks/MockedKeyringChecker.sol index 465a71c38..14dfa0117 100644 --- a/contracts/WhiteListing/mocks/MockedKeyringChecker.sol +++ b/contracts/KeyringChecker/mocks/MockedKeyringChecker.sol @@ -2,7 +2,7 @@ pragma solidity =0.8.19; -import { IKeyringChecker } from "../interfaces/IKeyringChecker.sol"; +import { IKeyringChecker } from "../IKeyringChecker.sol"; /// @title Mocked Keyring Checker /// @notice A mock implementation of IKeyringChecker for testing purposes From a8c8b6503c733154157f210031e8674629f9b257 Mon Sep 17 00:00:00 2001 From: Florian de Miramon Date: Tue, 18 Feb 2025 11:50:59 +0100 Subject: [PATCH 3/3] Change verification for transfer --- contracts/Comptroller/Diamond/facets/PolicyFacet.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/Comptroller/Diamond/facets/PolicyFacet.sol b/contracts/Comptroller/Diamond/facets/PolicyFacet.sol index 3fb9a850f..08fafce88 100644 --- a/contracts/Comptroller/Diamond/facets/PolicyFacet.sol +++ b/contracts/Comptroller/Diamond/facets/PolicyFacet.sol @@ -405,7 +405,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { address dst, uint256 transferTokens ) external returns (uint256) { - ensureUserAllowed(src); + ensureUserAllowed(dst); // Pausing is a very serious situation - we revert to sound the alarms checkProtocolPauseState(); checkActionPauseState(vToken, Action.TRANSFER); @@ -434,7 +434,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper { */ // solhint-disable-next-line no-unused-vars function transferVerify(address vToken, address src, address dst, uint256 transferTokens) external { - ensureUserAllowed(src); + ensureUserAllowed(dst); if (address(prime) != address(0)) { prime.accrueInterestAndUpdateScore(src, vToken); prime.accrueInterestAndUpdateScore(dst, vToken);