diff --git a/README.md b/README.md index 18f5df1..82d6896 100644 --- a/README.md +++ b/README.md @@ -42,15 +42,20 @@ This is an interface that must be implemented by the provider registry contract. Note: In both IProviderRegistry and IUserRegistry - some functions are restrictied to be called exclusively by the preconfimration contract. +## Whitelist + +To enable bridging to native ether, bridging contracts need be able to mint/burn native ether. The `Whitelist` is responsible for managing a whitelist of addresses that can mint/burn native ether. An admin account must be specified on deployment, who is the only address that can mutate the whitelist. + ## Tests The tests in this repository perform the following: -- Deployment of the `ProviderRegistry` and `UserRegistry` contracts. +- Deployment of the `ProviderRegistry`, `UserRegistry`, and `Whitelist` contracts. - Registration and staking of users and providers. - Verification of bid hashes and pre-confirmation commitment hashes. - Recovery of signer addresses. - Storage of valid commitments. +- Tests basic whitelisting functionality. To run the tests, use the following command: @@ -147,12 +152,17 @@ export PRIVATE_KEY="your-private-key" export CHAIN_ID=17864 ``` -- Run the deploy script +- Run the deploy script for core conracts ``` forge script scripts/DeployScripts.s.sol:DeployScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --chain-id $CHAIN_ID -vvvv ``` +- Run deploy script for whitelist contract, HYP_ERC20_ADDR denotes the HypERC20.sol contract address to give native mint/burn privileges. + +``` +HYP_ERC20_ADDR=0xBe3dEF3973584FdcC1326634aF188f0d9772D57D forge script scripts/DeployScripts.s.sol:DeployWhitelist --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --chain-id $CHAIN_ID -vvvv +``` #### Test Contracts diff --git a/contracts/Whitelist.sol b/contracts/Whitelist.sol new file mode 100644 index 0000000..1377a23 --- /dev/null +++ b/contracts/Whitelist.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: BSL 1.1 +pragma solidity ^0.8.15; + +// Contract that allows an admin to add/remove addresses from the whitelist, +// and allows whitelisted addresses to mint/burn native tokens. +contract Whitelist { + address public admin; + mapping(address => bool) public whitelistedAddresses; + + // Mint/burn precompile addresses. + // See: https://github.com/primevprotocol/go-ethereum/blob/03ae168c6ac15dda8c5a3f123e2b9f3350aad613/core/vm/contracts.go + address constant MINT = address(0x89); + address constant BURN = address(0x90); + + constructor(address _admin) { + require(_admin != address(0), "Admin address cannot be zero"); + admin = _admin; + } + + modifier onlyAdmin() { + require(msg.sender == admin, "Only admin can call this function"); + _; + } + + function addToWhitelist(address _address) external onlyAdmin { + whitelistedAddresses[_address] = true; + } + + function removeFromWhitelist(address _address) external onlyAdmin { + whitelistedAddresses[_address] = false; + } + + function isWhitelisted(address _address) public view returns (bool) { + return whitelistedAddresses[_address]; + } + + // Mints native tokens if the sender is whitelisted. + // See: https://github.com/primevprotocol/go-ethereum/blob/precompile-updates/core/vm/contracts_with_ctx.go#L83 + function mint(address _mintTo, uint256 _amount) external { + require(isWhitelisted(msg.sender), "Sender is not whitelisted"); + bool success; + (success, ) = MINT.call{value: 0, gas: gasleft()}( + abi.encode(_mintTo, _amount) + ); + require(success, "Native mint failed"); + } + + // Burns native tokens if the sender is whitelisted. + // See: https://github.com/primevprotocol/go-ethereum/blob/precompile-updates/core/vm/contracts_with_ctx.go#L111 + function burn(address _burnFrom, uint256 _amount) external { + require(isWhitelisted(msg.sender), "Sender is not whitelisted"); + bool success; + (success, ) = BURN.call{value: 0, gas: gasleft()}( + abi.encode(_burnFrom, _amount) + ); + require(success, "Native burn failed"); + } +} diff --git a/scripts/DeployScripts.s.sol b/scripts/DeployScripts.s.sol index 248e4ed..4a13270 100644 --- a/scripts/DeployScripts.s.sol +++ b/scripts/DeployScripts.s.sol @@ -5,7 +5,9 @@ import "contracts/UserRegistry.sol"; import "contracts/ProviderRegistry.sol"; import "contracts/PreConfirmations.sol"; import "contracts/Oracle.sol"; +import "contracts/Whitelist.sol"; +// Deploys core contracts contract DeployScript is Script { function run() external { vm.startBroadcast(); @@ -40,3 +42,36 @@ contract DeployScript is Script { vm.stopBroadcast(); } } + +// Deploys whitelist contract and adds HypERC20 to whitelist +contract DeployWhitelist is Script { + function run() external { + vm.startBroadcast(); + + address create2Proxy = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + require(isContractDeployed(create2Proxy), "Create2 proxy needs to be deployed. See https://github.com/primevprotocol/deterministic-deployment-proxy"); + + address hypERC20Addr = vm.envAddress("HYP_ERC20_ADDR"); + require(hypERC20Addr != address(0), "Whitelist address not provided"); + + // Forge deploy with salt uses create2 proxy from https://github.com/primevprotocol/deterministic-deployment-proxy + bytes32 salt = 0x8989000000000000000000000000000000000000000000000000000000000000; + address constDeployer = 0xBe3dEF3973584FdcC1326634aF188f0d9772D57D; + Whitelist whitelist = new Whitelist{salt: salt}(constDeployer); + console.log("Whitelist deployed to:", address(whitelist)); + console.log("Expected: 0xe57ee51bcb0914EC666703F923e0433d8c4d70b1"); + + whitelist.addToWhitelist(address(hypERC20Addr)); + console.log("Whitelist updated with hypERC20 address:", address(hypERC20Addr)); + + vm.stopBroadcast(); + } + + function isContractDeployed(address addr) public view returns (bool) { + uint size; + assembly { + size := extcodesize(addr) + } + return size > 0; + } +} diff --git a/test/WhitelistTest.sol b/test/WhitelistTest.sol new file mode 100644 index 0000000..bc8547d --- /dev/null +++ b/test/WhitelistTest.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: BSL 1.1 +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../contracts/Whitelist.sol"; + +// Tests the Whitelist contract. +// Note precompile interactions to mint/burn must be tested manually. +contract WhitelistTest is Test { + + address admin; + address normalUser; + address addressInstance; + Whitelist whitelist; + + function setUp() public { + admin = address(this); // Original contract deployer as admin + normalUser = address(0x100); + addressInstance = address(0x200); + whitelist = new Whitelist(admin); + } + + function test_IsWhitelisted() public { + assertFalse(whitelist.isWhitelisted(addressInstance)); + vm.prank(admin); + whitelist.addToWhitelist(addressInstance); + assertTrue(whitelist.isWhitelisted(addressInstance)); + } + + function test_AdminAddToWhitelist() public { + vm.prank(admin); + whitelist.addToWhitelist(addressInstance); + assertTrue(whitelist.isWhitelisted(addressInstance)); + } + + function test_AdminRemoveFromWhitelist() public { + vm.prank(admin); + whitelist.addToWhitelist(addressInstance); + assertTrue(whitelist.isWhitelisted(addressInstance)); + vm.prank(admin); + whitelist.removeFromWhitelist(addressInstance); + assertFalse(whitelist.isWhitelisted(addressInstance)); + } + + function test_RevertNormalUserAddToWhitelist() public { + vm.prank(normalUser); + vm.expectRevert("Only admin can call this function"); + whitelist.addToWhitelist(addressInstance); + } + + function test_RevertNormalUserRemoveFromWhitelist() public { + vm.prank(admin); + whitelist.addToWhitelist(addressInstance); + assertTrue(whitelist.isWhitelisted(addressInstance)); + vm.prank(normalUser); + vm.expectRevert("Only admin can call this function"); + whitelist.removeFromWhitelist(addressInstance); + } +}