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 4 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
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

**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.

- **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)

### Install
fbac marked this conversation as resolved.
Show resolved Hide resolved

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 providing 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"
```
fbac marked this conversation as resolved.
Show resolved Hide resolved
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 = "script/output/31337"},
{ access = "read-write", path = "script/output/11155111"},
{ access = "read-write", path = "script/output/241320161"}
]

[soldeer]
recursive_deps = true

[dependencies]
forge-std = "1.9.4"
"@openzeppelin-contracts-upgradeable" = "5.1.0"
"@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
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
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
10 changes: 10 additions & 0 deletions contracts/script/output/31337/group_messages_deployment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"addresses": {
"groupMessagesDeployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"groupMessagesImpl": "0xe8D2A1E88c91DCd5433208d4152Cc4F399a7e91d",
"groupMessagesProxy": "0x5067457698Fd6Fa1C6964e416b3f42713513B3dD",
"groupMessagesProxyAdmin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
},
"deploymentBlock": 100,
"latestUpgradeBlock": 100
}
10 changes: 10 additions & 0 deletions contracts/script/output/31337/identity_updates_deployment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"addresses": {
"identityUpdatesDeployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"identityUpdatesImpl": "0x18E317A7D70d8fBf8e6E893616b52390EbBdb629",
"identityUpdatesProxy": "0x4b6aB5F819A515382B0dEB6935D793817bB4af28",
"identityUpdatesProxyAdmin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
},
"deploymentBlock": 102,
"latestUpgradeBlock": 102
}
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