Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Interfaces/IUniversalCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
// =========================
Expand Down
64 changes: 63 additions & 1 deletion src/UniversalCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
_;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
214 changes: 214 additions & 0 deletions test/tests_token_and_core/UniversalCore.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

}