Skip to content

Commit

Permalink
Merge pull request #5 from omni-network/kh/cleanup
Browse files Browse the repository at this point in the history
chore(*): cleanup
  • Loading branch information
kevinhalliday authored Aug 8, 2024
2 parents 98a7a7a + d7c62c0 commit 969b2ae
Show file tree
Hide file tree
Showing 57 changed files with 736 additions and 14,824 deletions.
30 changes: 30 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
ifneq ("$(wildcard .env)","")
include .env
export $(shell sed 's/=.*//' .env)
endif

help: ## Display this help message
@egrep -h '\s##\s' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m %-30s\033[0m %s\n", $$1, $$2}'

ensure-deps:
@which omni > /dev/null 2>&1 || { \
echo "Binary `omni` not found. Installing..."; \
curl -sSfL https://raw.githubusercontent.com/omni-network/omni/main/scripts/install_omni_cli.sh | sh -s; \
}

build:
forge build

test:
forge test -vvv

devnet-start:
omni devnet start

devnet-clean:
omni devnet clean

devnet-deploy:
./script/devnet-deploy.sh

.PHONY: ensure-deps build test devnet-start devnet-clean deploy
191 changes: 30 additions & 161 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,192 +1,61 @@
# Omni Advanced Cross-Rollup Staking Example
# XStake

![Omni Unites Rollups](banner.png)
Cross-chain staking, built with Omni.

This repository contains an advanced example of a cross-chain dApp for staking tokens across multiple rollup networks. It showcases the use of an Omni contract for global state of cross-rollup staking operations and includes a frontend for user interactions.
This repository is meant as an example. It demonstrates how to accept ERC20 deposits
chains on multiple chains, and maintain global accounting on Omni.

## Repository Structure
These contracts are unaudited, and should not be used in production.

```
|
├── frontend # Frontend React application for the dApp
│ ├── README.md # README for the frontend directory
│ ├── package.json # NPM package configuration
│ ├── public # Public assets and HTML template
│ ├── src # Source code for the React application
│ └── tsconfig.json # TypeScript configuration
├── lib # Libraries and dependencies
│ ├── forge-std # Standard library for Foundry
│ ├── omni # Omni protocol libraries
│ └── openzeppelin-contracts # OpenZeppelin smart contract libraries
├── script # Deployment and utility scripts
│ ├── *.s.sol # Solidity contract deployment scripts
│ └── bash # Bash scripts for deployment and dapp setup
├── src # Solidity contracts source
│ ├── GlobalManager.sol
│ ├── LocalStake.sol
│ └── LocalToken.sol
├── test # Tests for contracts
| ├── GlobalManager.t.sol
| └── LocalStake.t.sol
├── foundry.toml # Foundry build and test configuration
└── ...
```
## How it works

## Contract Interaction
The protocol has two contracts

```
┌──────────────────────┐ ┌──────────────────────┐
│ │ │ │
│ Optimism │ │ Arbitrum │
│ │ │ │
│ │ │ │
│ LocalToken.sol ├───────┐ ┌──────┤ LocalToken.sol │
│ LocalStake.sol │ │ │ │ LocalStake.sol │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
└──────────▲───────────┘ (1) │ │ (1) └───────────▲──────────┘
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ ┌────────▼──────▼──────┐ │
│ │ │ │
│ │ Omni EVM │ │
│ │ │ │
│ │ │ │
└──────────┤ GlobalManager.sol ├───────────┘
(2) │ │ (2)
│ │
│ │
│ │
└──────────────────────┘
```
- [`XStaker`](./src/XStaker.sol)
- [`XStakeController`](./src/XStakeController.sol)

Networks `Arbitrum` and `Optimism` post updates to the `GlobalManager` contract deployed on Omni (1). `GlobalManager` aggregates the state for both networks and is responsible for delegating actions dependent on this global state.

In the contract implementations in `src/`, `GlobalManager` verifies its global state before delegating an `unstake` operation to any of the deployed `LocalStake` contracts in whichever network.
The first accepts deposits, and pays out withdrawals. The second maintains global accounting, and authorizes withdrawals. To learn how each contract works, read the source code. It's not long, and is commented generously. Read in the following order:

:warning: **Note:** `GlobalManager` requires knowing what addresses have the deployed staking contract and their network to perform validity checks on state before delegating actions.

### How State is Managed Globally by `GlobalManager`
1. [`XStaker.stake`](./src/XStaker.sol#L60)

`GlobalManager` holds the following state:
Entrypoint for staking. This function accepts deposits, and records them with the `XStakeController` via `xcall`.

```solidity
contract GlobalManager is XApp {
address public owner; // Owner of the contract
mapping(uint64 => address) public chainIdToContract; // Maps chain IDs to contract addresses
mapping(address => mapping(uint64 => uint256)) public userToChainIdToStake; // Maps user addresses and chain IDs to stake amounts
uint256 public totalStake; // Total staked amount across all chains

/// @notice Initializes the contract with the portal address and sets the owner
constructor(address portal) XApp(portal) {
owner = msg.sender;
}
2. [`XStakerController.recordStake`](./src/XStakerController.sol#36)

// further implementation...
```
Records stake. Only callable by a known `XStaker` contract on a supported chain.

The variables `chainIdToContract`, `userToChainIdToStake` and `totalStake` are updated as users add and remove stake across the configured rollups.
3. [`XStakerController.unstake`](./src/XStakerController.sol#47)

#### Staking
Entrypoint for unstaking. This function authorizes withdrawals, and directs a payout to the corresponding `XStaker` via `xcall`.

Whenever a user stakes on any rollup, the staking contract on that rollup accepts the staked ERC20 tokens and calls the Portal contract to update the `GlobalManager` contract calling the `addStake` function which performs the following changes to state:
4. [`XStaker.withdraw`](./src/XStaker.sol#L80)

```solidity
userToChainIdToStake[user][xmsg.sourceChainId] += amount; // Adds the stake to the user's total on the specified chain
totalStake += amount; // Updates the total stake across all chains
```
Withdraws stake back to the user. Only callable by the `XStakerController`.

#### Unstaking

Similarly, when a user unstakes on any rollup, the staking contract on that rollup first relays this information to the `GlobalManager` contract by calling the Portal first and calling the `removeStake` function on it which importantly **first performs validation checks against its state**.

Given these validation checks pass (user has enough balance), it then proceeds by:

```solidity
userToChainIdToStake[user][xmsg.sourceChainId] -= amount; // Deducts the stake from the user's total
totalStake -= amount; // Updates the total stake
xcall(xmsg.sourceChainId, xmsg.sender, data); // Makes the cross-chain call to remove the stake
```
## Testing

Here the `GlobalManager` contract is responsible for continuing the unstaking process because it is also responsible for performing the validation. This `xcall` then calls the contract where the unstaking operation came from, and completes it.
This example includes example solidity [tests](./test) . They make use of Omni's `MockPortal` utility to test cross chain interactions.


### Applying This Framework to Other Apps

This model can be similarly applied to any application that suffers from fragmentation by being deployed to multiple rollups. Now instead, state can be managed globally in a central place, and network support for the application can be added incrementally with no adjustments to existing logic.

Some examples:

- Lending, where assets can be aggregated across rollups
- Gas payments, where balances on one network can pay for those in another
- Games, where assets in one network can be used in another
- Launchpads, accessing deeper liquidity across the Ethereum ecosystem

## Contract Deployment

Because `GlobalManager` requires knowledge of staking contract addresses and networks to perform verification checks it should be deployed first. Then the staking contracts should be deployed by chain, and the `GlobalManager` contract's state can be updated with the deployed addresses which it can use for validation.

The ERC20 and staking contracts follow. The deployment script in `script/bash/deploy.sh` performs this sequence for deployment, namely:

1. Deploys the `GlobalManager` contract to the Omni EVM
2. Deploys the `LocalToken` contract and `LocalStake` to the first rollup (`Optimism` in this case)
3. Deploys the `LocalToken` contract and `LocalStake` to the second rollup (`Arbitrum` in this case)
4. Prints deployment addresses for all five contracts

To run this deployment, configure `script/bash/env.sh` first and then run:
Run tests with

```bash
sh script/bash/setup.sh
make test
```

This will deploy the contracts according to the configured environment and add the deployed addresses of the staking contracts to the `GlobalManager` contract (by running `chain.sh`).

## Run the App Locally

### Run Networks and Omni Locally

To run the app locally make sure you have a running version of the `omni` CLI that supports the `devnet start` command and start the local devnet with:

```sh
omni devnet start
```

### Contracts Setup

Having run this, you can find the address for the Portal contracts by running:

```bash
omni devnet info
```

Add this Portal contract address to `script/bash/env.sh`.

You will need a `PRIVATE_KEY` environment variable for running the scripts too, we can use the [`anvil`](https://book.getfoundry.sh/reference/anvil/) rich keys:

```bash
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```

Now you can run:

```
sh script/bash/setup.sh
```

The contract addresses will be shown on your terminal output.

### Frontend Setup

Ensure the addresses shown before match those seen in `frontend/src/constants/networks.ts`.
## Try it out

Then change directories into `frontend/` and run:
To try out the contracts, you can deploy them to a local Omni devnet.

```bash
yarn && yarn start
make devnet-start
make devnet-deploy
```

Use the previously used anvil rich key to sign into metamask and interact with the app :)
This deploys an `XStakerController` to Omni's devnet EVM. Along with an
`XStaker` to each mock rollup - mock arb and mock op. It also deploys an ERC20
staking token to each rollup. This token has a public `mint()` method, so you
can mint tokens to test with.
Binary file removed banner.png
Binary file not shown.
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
[fmt]
number_underscore = "thousands"
23 changes: 0 additions & 23 deletions frontend/.gitignore

This file was deleted.

90 changes: 0 additions & 90 deletions frontend/README.md

This file was deleted.

Binary file removed frontend/img/image.png
Binary file not shown.
Loading

0 comments on commit 969b2ae

Please sign in to comment.