Skip to content

Commit

Permalink
include finished contracts, ci and readme
Browse files Browse the repository at this point in the history
  • Loading branch information
fbac committed Dec 19, 2024
1 parent 531feae commit e29d1c0
Show file tree
Hide file tree
Showing 19 changed files with 427 additions and 172 deletions.
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.

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

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"
```
6 changes: 2 additions & 4 deletions contracts/script/DeployGroupMessages.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ contract DeployGroupMessages is Script, Utils, Environment {
require(address(groupMessagesImpl) != address(0), "Implementation deployment failed");

// Deploy the proxy contract.
proxy =
new ERC1967Proxy(
address(groupMessagesImpl),
abi.encodeWithSelector(GroupMessages.initialize.selector, admin)
proxy = new ERC1967Proxy(
address(groupMessagesImpl), abi.encodeWithSelector(GroupMessages.initialize.selector, admin)
);

vm.stopBroadcast();
Expand Down
5 changes: 1 addition & 4 deletions contracts/script/DeployIdentityUpdates.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ contract DeployIdentityUpdates is Script, Utils, Environment {

// Deploy the proxy contract.
proxy =
new ERC1967Proxy(
address(idUpdatesImpl),
abi.encodeWithSelector(IdentityUpdates.initialize.selector, admin)
);
new ERC1967Proxy(address(idUpdatesImpl), abi.encodeWithSelector(IdentityUpdates.initialize.selector, admin));

vm.stopBroadcast();

Expand Down
8 changes: 4 additions & 4 deletions contracts/script/output/31337/group_messages_deployment.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"addresses": {
"groupMessagesDeployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"groupMessagesImpl": "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A",
"groupMessagesProxy": "0xD5ac451B0c50B9476107823Af206eD814a2e2580",
"groupMessagesImpl": "0xe8D2A1E88c91DCd5433208d4152Cc4F399a7e91d",
"groupMessagesProxy": "0x5067457698Fd6Fa1C6964e416b3f42713513B3dD",
"groupMessagesProxyAdmin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
},
"deploymentBlock": 104,
"latestUpgradeBlock": 110
"deploymentBlock": 100,
"latestUpgradeBlock": 100
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"addresses": {
"identityUpdatesDeployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"identityUpdatesImpl": "0x18E317A7D70d8fBf8e6E893616b52390EbBdb629",
"identityUpdatesProxy": "0x5067457698Fd6Fa1C6964e416b3f42713513B3dD",
"identityUpdatesProxy": "0x4b6aB5F819A515382B0dEB6935D793817bB4af28",
"identityUpdatesProxyAdmin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
},
"deploymentBlock": 100,
"latestUpgradeBlock": 108
"deploymentBlock": 102,
"latestUpgradeBlock": 102
}
11 changes: 2 additions & 9 deletions contracts/script/upgrades/UpgradeGroupMessages.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ contract UpgradeGroupMessages is Script, Utils, Environment {
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"
);
require(proxy.hasRole(proxy.DEFAULT_ADMIN_ROLE(), upgrader), "Upgrader must have admin role");
}

function _serializeUpgradeData() internal {
Expand All @@ -50,10 +47,6 @@ contract UpgradeGroupMessages is Script, Utils, Environment {
getOutputPath(XMTP_GROUP_MESSAGES_OUTPUT_JSON),
".addresses.groupMessagesImpl"
);
vm.writeJson(
vm.toString(block.number),
getOutputPath(XMTP_GROUP_MESSAGES_OUTPUT_JSON),
".latestUpgradeBlock"
);
vm.writeJson(vm.toString(block.number), getOutputPath(XMTP_GROUP_MESSAGES_OUTPUT_JSON), ".latestUpgradeBlock");
}
}
11 changes: 2 additions & 9 deletions contracts/script/upgrades/UpgradeIdentityUpdates.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ contract UpgradeIdentityUpdates is Script, Utils, Environment {
string memory fileContent = readOutput(XMTP_IDENTITY_UPDATES_OUTPUT_JSON);
proxy = IdentityUpdates(stdJson.readAddress(fileContent, ".addresses.identityUpdatesProxy"));
require(address(proxy) != address(0), "proxy address not set");
require(
proxy.hasRole(proxy.DEFAULT_ADMIN_ROLE(), upgrader),
"Upgrader must have admin role"
);
require(proxy.hasRole(proxy.DEFAULT_ADMIN_ROLE(), upgrader), "Upgrader must have admin role");
}

function _serializeUpgradeData() internal {
Expand All @@ -50,10 +47,6 @@ contract UpgradeIdentityUpdates is Script, Utils, Environment {
getOutputPath(XMTP_IDENTITY_UPDATES_OUTPUT_JSON),
".addresses.identityUpdatesImpl"
);
vm.writeJson(
vm.toString(block.number),
getOutputPath(XMTP_IDENTITY_UPDATES_OUTPUT_JSON),
".latestUpgradeBlock"
);
vm.writeJson(vm.toString(block.number), getOutputPath(XMTP_IDENTITY_UPDATES_OUTPUT_JSON), ".latestUpgradeBlock");
}
}
2 changes: 1 addition & 1 deletion contracts/script/utils/Utils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ contract Utils is Script {
string memory outputFilePath = string.concat(outputDir, chainDir, outputFileName, ".json");
return outputFilePath;
}
}
}
41 changes: 31 additions & 10 deletions contracts/src/GroupMessages.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,79 @@ import "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

/// @title XMTP Group Messages Contract
contract GroupMessages is Initializable, AccessControlUpgradeable, UUPSUpgradeable, PausableUpgradeable {
/// @notice Emitted when a message is sent.
/// @param groupId The group ID.
/// @param message The message in bytes. Contains the full mls group message payload.
/// @param sequenceId The unique sequence ID of the message.
event MessageSent(bytes32 groupId, bytes message, uint64 sequenceId);
event UpgradeAuthorized(address deployer, address newImplementation);

error InvalidIdentityUpdateSize(uint256 actualSize, uint256 minSize, uint256 maxSize);
/// @notice Emitted when an upgrade is authorized.
/// @param upgrader The EOA authorizing the upgrade.
/// @param newImplementation The address of the new implementation.
event UpgradeAuthorized(address upgrader, address newImplementation);

// Custom errors
error ZeroAdminAddress();
error InvalidPayloadSize(uint256 actualSize, uint256 minSize, uint256 maxSize);

uint256 private constant MIN_PAYLOAD_SIZE = 78;
uint256 private constant MAX_PAYLOAD_SIZE = 4_194_304;
/// @dev Minimum valid payload size (in bytes).
uint256 public constant MIN_PAYLOAD_SIZE = 78;

error InvalidMessage();
/// @dev Maximum valid payload size (4 MB).
uint256 public constant MAX_PAYLOAD_SIZE = 4_194_304;

// State variables
uint64 private sequenceId;

/// @dev Reserved storage gap for future upgrades
uint256[50] private __gap;

// Initialization
/// @notice Initializes the contract with the deployer as admin.
/// @param _admin The address of the admin.
function initialize(address _admin) public initializer {
require(_admin != address(0), "Admin address cannot be zero");
require(_admin != address(0), ZeroAdminAddress());

__UUPSUpgradeable_init();
__AccessControl_init();
__Pausable_init();

_grantRole(DEFAULT_ADMIN_ROLE, _admin);
}

/// @notice Pauses the contract.
// Pausable functionality
/// @notice Pauses the contract, restricting certain actions.
/// @dev Callable only by accounts with the DEFAULT_ADMIN_ROLE.
function pause() public onlyRole(DEFAULT_ADMIN_ROLE) {
_pause();
}

/// @notice Unpauses the contract.
/// @notice Unpauses the contract, allowing normal operations.
/// @dev Callable only by accounts with the DEFAULT_ADMIN_ROLE.
function unpause() public onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}

// Messaging functionality
/// @notice Adds a message to the group.
/// @param groupId The group ID.
/// @param message The message in bytes.
/// @dev Ensures the message length is within the allowed range and increments the sequence ID.
function addMessage(bytes32 groupId, bytes calldata message) public whenNotPaused {
require(
message.length >= MIN_PAYLOAD_SIZE && message.length <= MAX_PAYLOAD_SIZE,
InvalidMessage()
InvalidPayloadSize(message.length, MIN_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE)
);

/// @dev Incrementing the sequence ID is safe here due to the extremely large limit of uint64.
// Increment sequence ID safely using unchecked to save gas.
unchecked {
sequenceId++;
}

emit MessageSent(groupId, message, sequenceId);
}

// Upgradeability
/// @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) {
Expand Down
Loading

0 comments on commit e29d1c0

Please sign in to comment.