-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Enhance XMTP protocol contracts #326
base: main
Are you sure you want to change the base?
Changes from all commits
9e50038
1e9953d
531feae
e29d1c0
695aafd
9b178c9
84c6aa2
163f389
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,9 @@ | |
*.so | ||
*.dylib | ||
|
||
# delve debugger | ||
__debug_* | ||
|
||
# build folder | ||
build/ | ||
bin/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,112 @@ | ||
## Foundry | ||
# XMTP Contracts | ||
|
||
**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** | ||
- [XMTP Contracts](#xmtp-contracts) | ||
- [Messages Contracts](#messages-contracts) | ||
- [XMTP Node Registry](#xmtp-node-registry) | ||
- [Usage](#usage) | ||
- [Prerequisites](#prerequisites) | ||
- [Install](#install) | ||
- [Test](#test) | ||
- [Run static analysis](#run-static-analysis) | ||
- [Scripts](#scripts) | ||
- [Messages contracts](#messages-contracts-1) | ||
- [Node registry](#node-registry) | ||
|
||
Foundry consists of: | ||
**⚠️ Experimental:** This software is in early development. Expect frequent changes and unresolved issues. | ||
Comment on lines
+1
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance introduction with critical architectural details The introduction should include essential information about:
# XMTP Contracts
- [XMTP Contracts](#xmtp-contracts)
- [Messages Contracts](#messages-contracts)
- [XMTP Node Registry](#xmtp-node-registry)
- [Usage](#usage)
- [Prerequisites](#prerequisites)
- [Install](#install)
- [Test](#test)
- [Run static analysis](#run-static-analysis)
- [Scripts](#scripts)
- [Messages contracts](#messages-contracts-1)
- [Node registry](#node-registry)
**⚠️ Experimental:** This software is in early development. Expect frequent changes and unresolved issues.
+
+## Architecture Overview
+
+The XMTP protocol implements upgradeable smart contracts using the UUPS (Universal Upgradeable Proxy Standard) pattern. The system consists of:
+
+- Message contracts (`GroupMessages`, `IdentityUpdates`) for managing network state
+- Node Registry contract for tracking network participants
+
+### Security Model
+
+The contracts implement role-based access control with the following trust assumptions:
+- Admin roles for contract upgrades and emergency functions
+- Node operator roles for network participation
+
+For detailed security considerations, see [SECURITY.md](./SECURITY.md).
|
||
|
||
- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). | ||
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. | ||
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. | ||
- **Chisel**: Fast, utilitarian, and verbose solidity REPL. | ||
This repository contains all the smart contracts that underpin the XMTP decentralized network. | ||
|
||
## Documentation | ||
## Messages Contracts | ||
|
||
https://book.getfoundry.sh/ | ||
The messages contracts manage the blockchain state for `GroupMessages` and `IdentityUpdates` sent by clients to the network. | ||
|
||
These contracts ensure transparency and provide a historical record of state changes. | ||
|
||
## XMTP Node Registry | ||
|
||
The `XMTP Node Registry` maintains a blockchain-based record of all node operators participating in the XMTP network. This registry serves as a source of truth for the network's active node participants, contributing to the network's integrity. | ||
|
||
The registry is currently implemented following the [ERC721](https://eips.ethereum.org/EIPS/eip-721) standard. | ||
fbac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Usage | ||
|
||
### Build | ||
The project is built with the `Foundry` framework, and dependency management is handled using `soldeer`. | ||
|
||
Additionally, it uses `slither` for static analysis. | ||
|
||
### Prerequisites | ||
|
||
[Install foundry](https://book.getfoundry.sh/getting-started/installation) | ||
|
||
[Install slither](https://github.com/crytic/slither?tab=readme-ov-file#how-to-install) | ||
|
||
fbac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
### Install | ||
|
||
As the project uses `soldeer`, update the dependencies by running: | ||
|
||
```shell | ||
$ forge build | ||
forge soldeer update | ||
``` | ||
|
||
### Test | ||
Build the contracts: | ||
|
||
```shell | ||
$ forge test | ||
forge build | ||
``` | ||
|
||
### Format | ||
### Test | ||
|
||
To run the unit tests: | ||
|
||
```shell | ||
$ forge fmt | ||
forge test | ||
``` | ||
|
||
### Gas Snapshots | ||
### Run static analysis | ||
|
||
Run the analysis with `slither`: | ||
|
||
```shell | ||
$ forge snapshot | ||
slither . | ||
``` | ||
|
||
### Anvil | ||
## Scripts | ||
|
||
The project includes deployer and upgrade scripts. | ||
|
||
### Messages contracts | ||
|
||
- Configure the environment by creating an `.env` file, with this content: | ||
|
||
```shell | ||
$ anvil | ||
### Main configuration | ||
PRIVATE_KEY=0xYourPrivateKey # Private key of the EOA deploying the contracts | ||
|
||
### XMTP deployment configuration | ||
XMTP_GROUP_MESSAGES_ADMIN_ADDRESS=0x12345abcdf # the EOA assuming the admin role in the GroupMessages contract. | ||
XMTP_IDENTITY_UPDATES_ADMIN_ADDRESS=0x12345abcdf # the EOA assuming the admin role in the IdentityUpdates contract. | ||
``` | ||
|
||
### Deploy | ||
- Run the desired script with: | ||
|
||
```shell | ||
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key> | ||
forge script --rpc-url <RPC_URL> --broadcast <PATH_TO_SCRIPT> | ||
``` | ||
|
||
### Cast | ||
Example: | ||
|
||
```shell | ||
$ cast <subcommand> | ||
forge script --rpc-url http://localhost:7545 --broadcast script/DeployGroupMessages.s.sol | ||
``` | ||
|
||
### Help | ||
The scripts output the deployment and upgrade in the `output` folder. | ||
|
||
### Node registry | ||
|
||
**⚠️:** The node registry hasn't been fully migrated to forge scripts. | ||
|
||
- Deploy with `forge create`: | ||
|
||
```shell | ||
$ forge --help | ||
$ anvil --help | ||
$ cast --help | ||
forge create --broadcast --legacy --json --rpc-url $DOCKER_RPC_URL --private-key $PRIVATE_KEY "src/Nodes.sol:Nodes" | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"addresses": { | ||
"groupMessagesDeployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", | ||
"groupMessagesImpl": "0x0a17FabeA4633ce714F1Fa4a2dcA62C3bAc4758d", | ||
"groupMessagesProxy": "0x3C1Cb427D20F15563aDa8C249E71db76d7183B6c", | ||
"groupMessagesProxyAdmin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" | ||
}, | ||
"deploymentBlock": 65, | ||
"latestUpgradeBlock": 71 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"addresses": { | ||
"identityUpdatesDeployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", | ||
"identityUpdatesImpl": "0x1343248Cbd4e291C6979e70a138f4c774e902561", | ||
"identityUpdatesProxy": "0x22a9B82A6c3D2BFB68F324B2e8367f346Dd6f32a", | ||
"identityUpdatesProxyAdmin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" | ||
}, | ||
"deploymentBlock": 67, | ||
"latestUpgradeBlock": 67 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,10 +7,21 @@ 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/", | ||
] | ||
fs_permissions = [ | ||
{ access = "read-write", path = "config/anvil_localnet"}, | ||
{ access = "read-write", path = "config/xmtp_testnet"}, | ||
{ access = "read-write", path = "config/unknown"} | ||
] | ||
|
||
[soldeer] | ||
recursive_deps = true | ||
|
||
[dependencies] | ||
forge-std = "1.9.4" | ||
"@openzeppelin-contracts-upgradeable" = "5.1.0" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there such a thing as dependabot for foundry? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're using |
||
"@openzeppelin-contracts" = "5.1.0" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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-upgradeable/=dependencies/@openzeppelin-contracts-upgradeable-5.1.0/ | ||
@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-5.1.0/ | ||
forge-std/=dependencies/forge-std-1.9.4/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.28; | ||
|
||
import "forge-std/src/Script.sol"; | ||
import "forge-std/src/Vm.sol"; | ||
import "./utils/Utils.sol"; | ||
import "./utils/Environment.sol"; | ||
import "src/GroupMessages.sol"; | ||
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; | ||
|
||
contract DeployGroupMessages is Script, Utils, Environment { | ||
GroupMessages groupMessagesImpl; | ||
ERC1967Proxy proxy; | ||
|
||
address admin; | ||
address deployer; | ||
|
||
function run() external { | ||
admin = vm.envAddress("XMTP_GROUP_MESSAGES_ADMIN_ADDRESS"); | ||
require(admin != address(0), "XMTP_GROUP_MESSAGES_ADMIN_ADDRESS not set"); | ||
require(admin.code.length == 0, "admin address is a contract, not an EOA"); | ||
|
||
uint256 privateKey = vm.envUint("PRIVATE_KEY"); | ||
deployer = vm.addr(privateKey); | ||
vm.startBroadcast(privateKey); | ||
|
||
// Deploy the implementation contract. | ||
groupMessagesImpl = new GroupMessages(); | ||
require(address(groupMessagesImpl) != address(0), "Implementation deployment failed"); | ||
|
||
// Deploy the proxy contract. | ||
proxy = new ERC1967Proxy( | ||
address(groupMessagesImpl), abi.encodeWithSelector(GroupMessages.initialize.selector, admin) | ||
); | ||
|
||
vm.stopBroadcast(); | ||
|
||
_serializeDeploymentData(); | ||
} | ||
|
||
function _serializeDeploymentData() internal { | ||
string memory parent_object = "parent object"; | ||
string memory addresses = "addresses"; | ||
|
||
string memory addressesOutput; | ||
|
||
addressesOutput = vm.serializeAddress(addresses, "groupMessagesDeployer", deployer); | ||
addressesOutput = vm.serializeAddress(addresses, "groupMessagesProxyAdmin", admin); | ||
addressesOutput = vm.serializeAddress(addresses, "groupMessagesProxy", address(proxy)); | ||
addressesOutput = vm.serializeAddress(addresses, "groupMessagesImpl", address(groupMessagesImpl)); | ||
|
||
string memory finalJson; | ||
finalJson = vm.serializeString(parent_object, addresses, addressesOutput); | ||
finalJson = vm.serializeUint(parent_object, "deploymentBlock", block.number); | ||
finalJson = vm.serializeUint(parent_object, "latestUpgradeBlock", block.number); | ||
|
||
writeOutput(finalJson, XMTP_GROUP_MESSAGES_OUTPUT_JSON); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// SPDX-License-Identifier: MIT | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have not seen SPDX headers in our other files. |
||
pragma solidity 0.8.28; | ||
|
||
import "forge-std/src/Script.sol"; | ||
import "forge-std/src/Vm.sol"; | ||
import "./utils/Utils.sol"; | ||
import "./utils/Environment.sol"; | ||
import "src/IdentityUpdates.sol"; | ||
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; | ||
|
||
contract DeployIdentityUpdates is Script, Utils, Environment { | ||
IdentityUpdates idUpdatesImpl; | ||
ERC1967Proxy proxy; | ||
|
||
address admin; | ||
address deployer; | ||
|
||
function run() external { | ||
admin = vm.envAddress("XMTP_IDENTITY_UPDATES_ADMIN_ADDRESS"); | ||
require(admin != address(0), "XMTP_IDENTITY_UPDATES_ADMIN_ADDRESS not set"); | ||
require(admin.code.length == 0, "admin address is a contract, not an EOA"); | ||
|
||
uint256 privateKey = vm.envUint("PRIVATE_KEY"); | ||
deployer = vm.addr(privateKey); | ||
vm.startBroadcast(privateKey); | ||
|
||
// Deploy the implementation contract. | ||
idUpdatesImpl = new IdentityUpdates(); | ||
require(address(idUpdatesImpl) != address(0), "Implementation deployment failed"); | ||
|
||
// Deploy the proxy contract. | ||
proxy = | ||
new ERC1967Proxy(address(idUpdatesImpl), abi.encodeWithSelector(IdentityUpdates.initialize.selector, admin)); | ||
|
||
vm.stopBroadcast(); | ||
|
||
_serializeDeploymentData(); | ||
} | ||
|
||
function _serializeDeploymentData() internal { | ||
string memory parent_object = "parent object"; | ||
string memory addresses = "addresses"; | ||
|
||
string memory addressesOutput; | ||
|
||
addressesOutput = vm.serializeAddress(addresses, "identityUpdatesDeployer", deployer); | ||
addressesOutput = vm.serializeAddress(addresses, "identityUpdatesProxyAdmin", admin); | ||
addressesOutput = vm.serializeAddress(addresses, "identityUpdatesProxy", address(proxy)); | ||
addressesOutput = vm.serializeAddress(addresses, "identityUpdatesImpl", address(idUpdatesImpl)); | ||
|
||
string memory finalJson; | ||
finalJson = vm.serializeString(parent_object, addresses, addressesOutput); | ||
finalJson = vm.serializeUint(parent_object, "deploymentBlock", block.number); | ||
finalJson = vm.serializeUint(parent_object, "latestUpgradeBlock", block.number); | ||
|
||
writeOutput(finalJson, XMTP_IDENTITY_UPDATES_OUTPUT_JSON); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a clever fix for the missing JSON file |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.28; | ||
|
||
import "forge-std/src/Script.sol"; | ||
import "forge-std/src/Vm.sol"; | ||
import "../utils/Utils.sol"; | ||
import "../utils/Environment.sol"; | ||
import "src/GroupMessages.sol"; | ||
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; | ||
|
||
contract UpgradeGroupMessages is Script, Utils, Environment { | ||
GroupMessages newImplementation; | ||
GroupMessages proxy; | ||
|
||
address upgrader; | ||
|
||
function run() external { | ||
uint256 privateKey = vm.envUint("PRIVATE_KEY"); | ||
upgrader = vm.addr(privateKey); | ||
|
||
vm.startBroadcast(privateKey); | ||
|
||
_initializeProxy(); | ||
fbac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Deploy the new implementation contract. | ||
newImplementation = new GroupMessages(); | ||
require(address(newImplementation) != address(0), "Implementation deployment failed"); | ||
|
||
// Upgrade the proxy pointer to the new implementation. | ||
proxy.upgradeToAndCall(address(newImplementation), ""); | ||
|
||
vm.stopBroadcast(); | ||
|
||
_serializeUpgradeData(); | ||
} | ||
|
||
function _initializeProxy() internal { | ||
string memory fileContent = readOutput(XMTP_GROUP_MESSAGES_OUTPUT_JSON); | ||
proxy = GroupMessages(stdJson.readAddress(fileContent, ".addresses.groupMessagesProxy")); | ||
require(address(proxy) != address(0), "proxy address not set"); | ||
require(proxy.hasRole(proxy.DEFAULT_ADMIN_ROLE(), upgrader), "Upgrader must have admin role"); | ||
} | ||
|
||
function _serializeUpgradeData() internal { | ||
vm.writeJson( | ||
vm.toString(address(newImplementation)), | ||
getOutputPath(XMTP_GROUP_MESSAGES_OUTPUT_JSON), | ||
".addresses.groupMessagesImpl" | ||
); | ||
vm.writeJson(vm.toString(block.number), getOutputPath(XMTP_GROUP_MESSAGES_OUTPUT_JSON), ".latestUpgradeBlock"); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Security documentation is missing from the repository
The repository lacks a SECURITY.md file or any other security-related documentation. Consider adding comprehensive security documentation that covers vulnerability disclosure processes, security considerations, and best practices for the XMTP contracts.
🔗 Analysis chain
Verify security documentation exists
The suggested changes reference a
SECURITY.md
file. Please ensure this file exists and contains comprehensive security documentation.Let me verify if there are any other security-related documentation files in the repository.
Let me try one more search to check for any security-related content in the documentation.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 130
Script:
Length of output: 87
Script:
Length of output: 105