diff --git a/contracts/.gitignore b/contracts/.gitignore index ebe566f2..704b55a4 100644 --- a/contracts/.gitignore +++ b/contracts/.gitignore @@ -4,6 +4,8 @@ out/ # Ignores development broadcast logs !/broadcast +/broadcast/*/11155111/ +/broadcast/*/241320161/ /broadcast/*/31337/ /broadcast/**/dry-run/ diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 66cb3155..acf06f0e 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -7,10 +7,16 @@ libs = ["dependencies"] gas_reports = ["*"] optimizer = true optimizer_runs = 10_000 +remappings = [ + "@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-5.1.0/", + "@openzeppelin-contracts-upgradeable/=dependencies/@openzeppelin-contracts-upgradeable-5.1.0/", + "forge-std/=dependencies/forge-std-1.9.4/", +] [soldeer] recursive_deps = true [dependencies] forge-std = "1.9.4" +"@openzeppelin-contracts-upgradeable" = "5.1.0" "@openzeppelin-contracts" = "5.1.0" diff --git a/contracts/remappings.txt b/contracts/remappings.txt index 8b4a64d6..a1aecfd1 100644 --- a/contracts/remappings.txt +++ b/contracts/remappings.txt @@ -1,2 +1,3 @@ -@openzeppelin-contracts-5.1.0/=dependencies/@openzeppelin-contracts-5.1.0/ -forge-std-1.9.4/=dependencies/forge-std-1.9.4/ +@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-5.1.0/ +@openzeppelin-contracts-upgradeable/=dependencies/@openzeppelin-contracts-upgradeable-5.1.0/ +forge-std/=dependencies/forge-std-1.9.4/ diff --git a/contracts/script/DeployerGroupMessages.s.sol b/contracts/script/DeployerGroupMessages.s.sol new file mode 100644 index 00000000..8799afaa --- /dev/null +++ b/contracts/script/DeployerGroupMessages.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import "forge-std/src/Script.sol"; +import "src/GroupMessages.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +contract DeployProxiedGroupMessages is Script { + function run() external { + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(privateKey); + + // Step 1: Deploy the implementation contract + GroupMessages groupMessagesImpl = new GroupMessages(); + + // Step 2: Deploy the proxy contract + ERC1967Proxy proxy = new ERC1967Proxy( + address(groupMessagesImpl), + abi.encodeWithSelector(GroupMessages.initialize.selector) + ); + + // Log the deployed contract addresses + console.log("Implementation Address:", address(groupMessagesImpl)); + console.log("Proxy Address:", address(proxy)); + + vm.stopBroadcast(); + } +} diff --git a/contracts/script/Deployer.s.sol b/contracts/script/DeployerNodeRegistry.s.sol similarity index 80% rename from contracts/script/Deployer.s.sol rename to contracts/script/DeployerNodeRegistry.s.sol index e16949d3..361ab3db 100644 --- a/contracts/script/Deployer.s.sol +++ b/contracts/script/DeployerNodeRegistry.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.28; -import {Script, console} from "forge-std-1.9.4/src/Script.sol"; +import {Script, console} from "forge-std/src/Script.sol"; import "../src/Nodes.sol"; contract Deployer is Script { diff --git a/contracts/soldeer.lock b/contracts/soldeer.lock index ecca7c99..7c7c4295 100644 --- a/contracts/soldeer.lock +++ b/contracts/soldeer.lock @@ -5,6 +5,13 @@ url = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/5_1_0_ checksum = "fd3d1ea561cb27897008aee18ada6e85f248eb161c86e4435272fc2b5777574f" integrity = "cb6cf6e878f2943b2291d5636a9d72ac51d43d8135896ceb6cf88d36c386f212" +[[dependencies]] +name = "@openzeppelin-contracts-upgradeable" +version = "5.1.0" +url = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts-upgradeable/5_1_0_19-10-2024_10:28:58_contracts-upgradeable.zip" +checksum = "87854223d14941d6fda3d78d900217e79e25755ea5bc48beca035766fa6a4e7e" +integrity = "826fb621339dcee4261f848b283ec86364743d3c289d61f621747d95e315215a" + [[dependencies]] name = "forge-std" version = "1.9.4" diff --git a/contracts/src/GroupMessages.sol b/contracts/src/GroupMessages.sol index 39b40c16..015d3017 100644 --- a/contracts/src/GroupMessages.sol +++ b/contracts/src/GroupMessages.sol @@ -1,14 +1,51 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.28; -contract GroupMessages { +import "@openzeppelin-contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import "@openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin-contracts-upgradeable/utils/PausableUpgradeable.sol"; +import "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +/// @title XMTP Group Messages Contract +contract GroupMessages is Initializable, AccessControlUpgradeable, UUPSUpgradeable, PausableUpgradeable { event MessageSent(bytes32 groupId, bytes message, uint64 sequenceId); + event UpgradeAuthorized(address deployer, address newImplementation); - uint64 sequenceId; + uint64 private sequenceId; - function addMessage(bytes32 groupId, bytes memory message) public { - sequenceId++; + /// @notice Initializes the contract with the deployer as admin. + function initialize() public initializer { + __UUPSUpgradeable_init(); + __AccessControl_init(); + __Pausable_init(); + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + /// @notice Pauses the contract. + function pause() public onlyRole(DEFAULT_ADMIN_ROLE) { + _pause(); + } + + /// @notice Unpauses the contract. + function unpause() public onlyRole(DEFAULT_ADMIN_ROLE) { + _unpause(); + } + + /// @notice Adds a message to the group. + /// @param groupId The group ID. + /// @param message The message in bytes. + function addMessage(bytes32 groupId, bytes calldata message) public whenNotPaused { + /// @dev Incrementing the sequence ID is safe here due to the extremely large limit of uint64. + unchecked { + sequenceId++; + } emit MessageSent(groupId, message, sequenceId); } + + /// @dev Authorizes the upgrade of the contract. + /// @param newImplementation The address of the new implementation. + function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) { + emit UpgradeAuthorized(msg.sender, newImplementation); + } } diff --git a/contracts/src/IdentityUpdates.sol b/contracts/src/IdentityUpdates.sol index 02017a32..4eac7aa9 100644 --- a/contracts/src/IdentityUpdates.sol +++ b/contracts/src/IdentityUpdates.sol @@ -1,14 +1,51 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.28; -contract IdentityUpdates { +import "@openzeppelin-contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import "@openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin-contracts-upgradeable/utils/PausableUpgradeable.sol"; +import "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +/// @title XMTP Identity Updates Contract +contract IdentityUpdates is Initializable, AccessControlUpgradeable, UUPSUpgradeable, PausableUpgradeable { event IdentityUpdateCreated(bytes32 inboxId, bytes update, uint64 sequenceId); + event UpgradeAuthorized(address deployer, address newImplementation); - uint64 sequenceId; + uint64 private sequenceId; - function addIdentityUpdate(bytes32 inboxId, bytes memory update) public { - sequenceId++; + /// @notice Initializes the contract with the deployer as admin. + function initialize() public initializer { + __UUPSUpgradeable_init(); + __AccessControl_init(); + __Pausable_init(); + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + /// @notice Pauses the contract. + function pause() public onlyRole(DEFAULT_ADMIN_ROLE) { + _pause(); + } + + /// @notice Unpauses the contract. + function unpause() public onlyRole(DEFAULT_ADMIN_ROLE) { + _unpause(); + } + + /// @notice Adds an identity update to an specific inbox ID. + /// @param inboxId The inbox ID. + /// @param update The identity update in bytes. + function addIdentityUpdate(bytes32 inboxId, bytes calldata update) public whenNotPaused { + /// @dev Incrementing the sequence ID is safe here due to the extremely large limit of uint64. + unchecked { + sequenceId++; + } emit IdentityUpdateCreated(inboxId, update, sequenceId); } + + /// @dev Authorizes the upgrade of the contract. + /// @param newImplementation The address of the new implementation. + function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) { + emit UpgradeAuthorized(msg.sender, newImplementation); + } } diff --git a/contracts/src/Nodes.sol b/contracts/src/Nodes.sol index b7d2d0fb..bcea51a7 100644 --- a/contracts/src/Nodes.sol +++ b/contracts/src/Nodes.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.28; -import "@openzeppelin-contracts-5.1.0/token/ERC721/ERC721.sol"; -import "@openzeppelin-contracts-5.1.0/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; /** * A NFT contract for XMTP Node Operators. diff --git a/contracts/test/GroupMessage.t.sol b/contracts/test/GroupMessage.t.sol index e1f761cd..4e5ce2f4 100644 --- a/contracts/test/GroupMessage.t.sol +++ b/contracts/test/GroupMessage.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.28; -import {Test, console} from "forge-std-1.9.4/src/Test.sol"; +import {Test, console} from "forge-std/src/Test.sol"; import {GroupMessages} from "../src/GroupMessages.sol"; contract GroupMessagesTest is Test { diff --git a/contracts/test/IdentityUpdates.t.sol b/contracts/test/IdentityUpdates.t.sol index a4d17fa8..f46e1e3e 100644 --- a/contracts/test/IdentityUpdates.t.sol +++ b/contracts/test/IdentityUpdates.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.28; -import {Test, console} from "forge-std-1.9.4/src/Test.sol"; +import {Test, console} from "forge-std/src/Test.sol"; import {IdentityUpdates} from "../src/IdentityUpdates.sol"; contract IdentityUpdatesTest is Test { diff --git a/contracts/test/Nodes.sol b/contracts/test/Nodes.sol index df4b21a5..0c9e7015 100644 --- a/contracts/test/Nodes.sol +++ b/contracts/test/Nodes.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.28; -import {Test, console} from "forge-std-1.9.4/src/Test.sol"; -import {Ownable} from "@openzeppelin-contracts-5.1.0/access/Ownable.sol"; +import {Test, console} from "forge-std/src/Test.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Nodes} from "../src/Nodes.sol"; contract NodesTest is Test {