-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from omni-network/kh/cleanup
chore(*): cleanup
- Loading branch information
Showing
57 changed files
with
736 additions
and
14,824 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Binary file not shown.
Oops, something went wrong.