From 46143f5c4aa576f37cc1f3b9fb3cc1cbf7cc5136 Mon Sep 17 00:00:00 2001 From: Harsh Rajat Date: Mon, 8 Dec 2025 08:26:27 +0530 Subject: [PATCH] Changelog UniversalGatewayPC.sol - public getters added PC20_PROTOCOL_FEES, PC721_PROTOCOL_FEES, DEFAULT_PROTOCOL_FEES - setter setProtocolFees added with onlyUEModule modifier - event ProtocolFeesUpdated added - added testcases - added isPC20SupportedOnChain, isPC721SupportedOnChain mapping - added functions setPC20SupportOnChain, setPC721SupportOnChain - added testcases for the same - changed gap to 45 since 3 uint and 2 mapping are added (at the bottom of the variables decleration) --- src/Interfaces/IUniversalCore.sol | 3 + src/UniversalCore.sol | 64 +++++- test/tests_token_and_core/UniversalCore.t.sol | 214 ++++++++++++++++++ 3 files changed, 280 insertions(+), 1 deletion(-) diff --git a/src/Interfaces/IUniversalCore.sol b/src/Interfaces/IUniversalCore.sol index 54a07de..95187ec 100644 --- a/src/Interfaces/IUniversalCore.sol +++ b/src/Interfaces/IUniversalCore.sol @@ -12,6 +12,9 @@ interface IUniversalCore { event SetGasToken(string chainId, address prc20); event SetDefaultDeadlineMins(uint256 minutesValue); event SetSupportedToken(address indexed prc20, bool supported); + event ProtocolFeesUpdated(uint256 pc20ProtocolFees, uint256 pc721ProtocolFees, uint256 defaultProtocolFees); + event SetPC20Support(string indexed chainNamespace, bool supported); + event SetPC721Support(string indexed chainNamespace, bool supported); event SetGasPCPool(string chainId, address pool, uint24 fee); event DepositPRC20WithAutoSwap(address prc20, uint256 amountIn, address pcToken, uint256 amountOut, uint24 fee, address target); // ========================= diff --git a/src/UniversalCore.sol b/src/UniversalCore.sol index 5941245..8b5a22f 100644 --- a/src/UniversalCore.sol +++ b/src/UniversalCore.sol @@ -73,6 +73,15 @@ contract UniversalCore is /// @notice Mapping for indicating an official PRC20 supported token mapping(address => bool) public isSupportedToken; + /// @notice Protocol Fees for PC20, PC721 or default + uint256 public PC20_PROTOCOL_FEES; + uint256 public PC721_PROTOCOL_FEES; + uint256 public DEFAULT_PROTOCOL_FEES; + + /// @notice Map to know if PC20 or if PC721 support for a given chain namespace + mapping(string => bool) public isPC20SupportedOnChain; + mapping(string => bool) public isPC721SupportedOnChain; + modifier onlyUEModule() { if (msg.sender != UNIVERSAL_EXECUTOR_MODULE) revert UniversalCoreErrors.CallerIsNotUEModule(); _; @@ -198,6 +207,59 @@ contract UniversalCore is emit DepositPRC20WithAutoSwap(prc20, amount, wPCContractAddress, pcOut, fee, target); } + /** + * @notice Set protocol fees for PC20, PC721 and default + * @dev Only addresses with MANAGER_ROLE can call this + * @param pc20Fee Protocol fee for PC20 tokens + * @param pc721Fee Protocol fee for PC721 tokens + * @param defaultFee Default protocol fee for other cases + */ + function setProtocolFees( + uint256 pc20Fee, + uint256 pc721Fee, + uint256 defaultFee + ) external onlyRole(MANAGER_ROLE) whenNotPaused { + PC20_PROTOCOL_FEES = pc20Fee; + PC721_PROTOCOL_FEES = pc721Fee; + DEFAULT_PROTOCOL_FEES = defaultFee; + + emit ProtocolFeesUpdated(pc20Fee, pc721Fee, defaultFee); + } + + /** + * @notice Enables or disables PC20 asset support for a given external chain namespace. + * @dev @dev Only addresses with MANAGER_ROLE can modify support. + * Paused state prevents changes. + * This does not deploy or register any token; it only flips support metadata. + * @param chainNamespace The chain namespace identifier (e.g., "eip155:1"). + * @param supported True to enable PC20 support, false to disable. + */ + function setPC20SupportOnChain(string calldata chainNamespace, bool supported) + external + onlyRole(MANAGER_ROLE) + whenNotPaused + { + isPC20SupportedOnChain[chainNamespace] = supported; + emit SetPC20Support(chainNamespace, supported); + } + + /** + * @notice Enables or disables PC721 (NFT) asset support for a given external chain namespace. + * @dev Only addresses with MANAGER_ROLE can modify support. + * Paused state prevents changes. + * This is purely metadata and does not deploy or map NFT contracts. + * @param chainNamespace The chain namespace identifier (e.g., "eip155:1"). + * @param supported True to enable PC721 support, false to disable. + */ + function setPC721SupportOnChain(string calldata chainNamespace, bool supported) + external + onlyRole(MANAGER_ROLE) + whenNotPaused + { + isPC721SupportedOnChain[chainNamespace] = supported; + emit SetPC721Support(chainNamespace, supported); + } + /** * @dev Set the gas PC pool for a chain * @param chainID Chain ID @@ -414,5 +476,5 @@ contract UniversalCore is * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[50] private __gap; + uint256[45] private __gap; } diff --git a/test/tests_token_and_core/UniversalCore.t.sol b/test/tests_token_and_core/UniversalCore.t.sol index da9302f..2272250 100644 --- a/test/tests_token_and_core/UniversalCore.t.sol +++ b/test/tests_token_and_core/UniversalCore.t.sol @@ -60,6 +60,10 @@ contract UniversalCoreTest is Test, UpgradeableContractHelper { event Paused(address account); event Unpaused(address account); event SetSupportedToken(address indexed prc20, bool supported); + event ProtocolFeesUpdated(uint256 pc20ProtocolFees, uint256 pc721ProtocolFees, uint256 defaultProtocolFees); + event SetPC20Support(string indexed chainNamespace, bool supported); + event SetPC721Support(string indexed chainNamespace, bool supported); + function setUp() public { // Setup accounts @@ -844,4 +848,214 @@ contract UniversalCoreTest is Test, UpgradeableContractHelper { ); universalCore.setSupportedToken(token, true); } + + // ======================================== + // 7) Protocol fee configuration (MANAGER_ROLE) + // ======================================== + + function test_SetProtocolFees_OnlyManager() public { + uint256 pc20Fee = 123; + uint256 pc721Fee = 234; + uint256 defaultFee = 345; + + // Non manager should revert + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + nonUEModule, + universalCore.MANAGER_ROLE() + ) + ); + vm.prank(nonUEModule); + universalCore.setProtocolFees(pc20Fee, pc721Fee, defaultFee); + + // MANAGER_ROLE (UNIVERSAL_EXECUTOR_MODULE) should succeed + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + universalCore.setProtocolFees(pc20Fee, pc721Fee, defaultFee); + + // Verify stored values + assertEq(universalCore.PC20_PROTOCOL_FEES(), pc20Fee); + assertEq(universalCore.PC721_PROTOCOL_FEES(), pc721Fee); + assertEq(universalCore.DEFAULT_PROTOCOL_FEES(), defaultFee); + } + + function test_SetProtocolFees_EmitsEvent() public { + uint256 pc20Fee = 222; + uint256 pc721Fee = 333; + uint256 defaultFee = 444; + + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + vm.expectEmit(false, false, false, true); + emit ProtocolFeesUpdated(pc20Fee, pc721Fee, defaultFee); + universalCore.setProtocolFees(pc20Fee, pc721Fee, defaultFee); + } + + function test_ProtocolFees_DefaultZero() public view { + // By default, before any setProtocolFees call, all should be zero + assertEq(universalCore.PC20_PROTOCOL_FEES(), 0); + assertEq(universalCore.PC721_PROTOCOL_FEES(), 0); + assertEq(universalCore.DEFAULT_PROTOCOL_FEES(), 0); + } + + function test_ProtocolFees_AfterSetReflectStoredValues() public { + uint256 pc20Fee = 123; + uint256 pc721Fee = 456; + uint256 defaultFee = 789; + + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + universalCore.setProtocolFees(pc20Fee, pc721Fee, defaultFee); + + // Read back via public getters + assertEq(universalCore.PC20_PROTOCOL_FEES(), pc20Fee); + assertEq(universalCore.PC721_PROTOCOL_FEES(), pc721Fee); + assertEq(universalCore.DEFAULT_PROTOCOL_FEES(), defaultFee); + } + + function test_SetProtocolFees_WhenPaused_Reverts() public { + uint256 pc20Fee = 1e15; + uint256 pc721Fee = 2e15; + uint256 defaultFee = 5e14; + + // Pause by owner + vm.prank(deployer); + universalCore.pause(); + + // Manager cannot change fees while paused + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + vm.expectRevert( + abi.encodeWithSelector(PausableUpgradeable.EnforcedPause.selector) + ); + universalCore.setProtocolFees(pc20Fee, pc721Fee, defaultFee); + } + + // ======================================== + // 8) PC20 / PC721 chain support mapping (MANAGER_ROLE) + // ======================================== + + function test_PC20SupportOnChain_DefaultFalse() public view { + // By default, nothing is supported + assertFalse(universalCore.isPC20SupportedOnChain("eip155:1")); + assertFalse(universalCore.isPC20SupportedOnChain("eip155:8453")); + } + + function test_PC721SupportOnChain_DefaultFalse() public view { + // By default, nothing is supported + assertFalse(universalCore.isPC721SupportedOnChain("eip155:1")); + assertFalse(universalCore.isPC721SupportedOnChain("eip155:8453")); + } + + function test_SetPC20SupportOnChain_OnlyManagerRole() public { + string memory ns = "eip155:1"; + + // Non manager should revert + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + nonUEModule, + universalCore.MANAGER_ROLE() + ) + ); + vm.prank(nonUEModule); + universalCore.setPC20SupportOnChain(ns, true); + + // MANAGER_ROLE (UNIVERSAL_EXECUTOR_MODULE) should succeed + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + universalCore.setPC20SupportOnChain(ns, true); + + assertTrue(universalCore.isPC20SupportedOnChain(ns)); + } + + function test_SetPC721SupportOnChain_OnlyManagerRole() public { + string memory ns = "eip155:1"; + + // Non manager should revert + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + nonUEModule, + universalCore.MANAGER_ROLE() + ) + ); + vm.prank(nonUEModule); + universalCore.setPC721SupportOnChain(ns, true); + + // MANAGER_ROLE (UNIVERSAL_EXECUTOR_MODULE) should succeed + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + universalCore.setPC721SupportOnChain(ns, true); + + assertTrue(universalCore.isPC721SupportedOnChain(ns)); + } + + function test_SetPC20SupportOnChain_EmitsEvent() public { + string memory ns = "eip155:1"; + + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + vm.expectEmit(true, false, false, true); + emit SetPC20Support(ns, true); + universalCore.setPC20SupportOnChain(ns, true); + } + + function test_SetPC721SupportOnChain_EmitsEvent() public { + string memory ns = "eip155:8453"; + + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + vm.expectEmit(true, false, false, true); + emit SetPC721Support(ns, true); + universalCore.setPC721SupportOnChain(ns, true); + } + + function test_SetPC20SupportOnChain_ToggleTrueFalse() public { + string memory ns = "eip155:1"; + + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + universalCore.setPC20SupportOnChain(ns, true); + assertTrue(universalCore.isPC20SupportedOnChain(ns)); + + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + universalCore.setPC20SupportOnChain(ns, false); + assertFalse(universalCore.isPC20SupportedOnChain(ns)); + } + + function test_SetPC721SupportOnChain_ToggleTrueFalse() public { + string memory ns = "eip155:8453"; + + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + universalCore.setPC721SupportOnChain(ns, true); + assertTrue(universalCore.isPC721SupportedOnChain(ns)); + + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + universalCore.setPC721SupportOnChain(ns, false); + assertFalse(universalCore.isPC721SupportedOnChain(ns)); + } + + function test_SetPC20SupportOnChain_WhenPaused_Reverts() public { + string memory ns = "eip155:1"; + + // Pause by owner + vm.prank(deployer); + universalCore.pause(); + + // Manager cannot mutate while paused + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + vm.expectRevert( + abi.encodeWithSelector(PausableUpgradeable.EnforcedPause.selector) + ); + universalCore.setPC20SupportOnChain(ns, true); + } + + function test_SetPC721SupportOnChain_WhenPaused_Reverts() public { + string memory ns = "eip155:8453"; + + // Pause by owner + vm.prank(deployer); + universalCore.pause(); + + // Manager cannot mutate while paused + vm.prank(UNIVERSAL_EXECUTOR_MODULE); + vm.expectRevert( + abi.encodeWithSelector(PausableUpgradeable.EnforcedPause.selector) + ); + universalCore.setPC721SupportOnChain(ns, true); + } + }