Skip to content
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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
*.so
*.dylib

# delve debugger
__debug_*

# build folder
build/
bin/
Expand Down
2 changes: 2 additions & 0 deletions contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/11155111/
/broadcast/*/241320161/
/broadcast/*/31337/
/broadcast/**/dry-run/

Expand Down
100 changes: 73 additions & 27 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,112 @@
## Foundry
# XMTP Contracts
Copy link

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:

#!/bin/bash
# Check if SECURITY.md exists in the repository
if [ -f "contracts/SECURITY.md" ] || [ -f "SECURITY.md" ]; then
    echo "Security documentation found"
else
    echo "Security documentation missing"
fi

Length of output: 130


Script:

#!/bin/bash
# Search for any security-related documentation files
fd -i -e md security
fd -i -e txt security

# Search for security documentation sections in README files
fd README.md -x grep -il "security" {}

Length of output: 87


Script:

#!/bin/bash
# Search for any files containing security-related content
rg -i "security|vulnerability|disclosure" -g "*.md" -g "*.txt" --type-add 'docs:*.{md,txt}' --type docs

Length of output: 105


**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
Copy link

Choose a reason for hiding this comment

The 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:

  1. The security model and trust assumptions
  2. The upgradeability pattern (UUPS) mentioned in the AI summary
  3. The relationship and interaction between different contracts
 # 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).

Committable suggestion skipped: line range outside the PR's diff.


- **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"
```
10 changes: 10 additions & 0 deletions contracts/config/anvil_localnet/GroupMessages.json
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
}
10 changes: 10 additions & 0 deletions contracts/config/anvil_localnet/IdentityUpdates.json
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
}
11 changes: 11 additions & 0 deletions contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there such a thing as dependabot for foundry?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're using slither for that purpose, whenever it finds a dependency with open CVE it raises and alert.

"@openzeppelin-contracts" = "5.1.0"
5 changes: 3 additions & 2 deletions contracts/remappings.txt
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/
59 changes: 59 additions & 0 deletions contracts/script/DeployGroupMessages.s.sol
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);
}
}
58 changes: 58 additions & 0 deletions contracts/script/DeployIdentityUpdates.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
Copy link
Collaborator

Choose a reason for hiding this comment

The 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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: UNLICENSED
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {Script, console} from "forge-std-1.9.4/src/Script.sol";
import "../src/Nodes.sol";
import {Script, console} from "forge-std/src/Script.sol";
import "src/Nodes.sol";

contract Deployer is Script {
function setUp() public {}
Expand Down
52 changes: 52 additions & 0 deletions contracts/script/upgrades/UpgradeGroupMessages.s.sol
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");
}
}
Loading
Loading