Skip to content

Commit

Permalink
fix: last tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
georgeroman committed Nov 16, 2024
1 parent f95461f commit fe25f95
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 58 deletions.
86 changes: 32 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,44 @@
## Foundry
## Relay Pools

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
Relay’s vision for cross chain liquidity is that a network of solvers fill user requests instantly with their own capital. That said, not all cross chain orderflow can be filled by solvers:

Foundry consists of:
- solvers won’t always have enough liquidity, especially for large orders, or long tail chains
- solvers themselves need to rebalance inventory

- **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.
Today, both of these are solved by canonical bridges:

## Documentation
- for large / exotic requests, users directly use the bridge instead of solver
- solvers use the bridge to rebalance

https://book.getfoundry.sh/
What’s nice about canonical bridges is that they are low cost and support unlimited size. The main downside is speed, sometimes taking up to 7 days.

## Usage
We think there is an opportunity for something in the middle. Not everyone who holds capital can run a solver, but they can contribute it to a pool, resulting in more available liquidity. Due to the onchain nature of pools, it’s hard to make them as fast as a solver, but they can definitely be much faster than the canonical bridge. And so you unlock improvements for both users and solvers:

### Build
- users get more tolerable speed (30 seconds) when there’s no instant solver liquidity
- solvers can fast rebalance against a pool for higher throughput

```shell
$ forge build
```
While there are already other pool based bridges available (Stargate, Across, etc), the idea is that none of these fully optimize for a world dominated by sophisticated solvers and long tail chains, so there is a big gap in the market.

### Test
The rough idea is that Relay pools are used to “accelerate” any existing bridge:

```shell
$ forge test
```
- users send money over a bridge, but via a proxy contract
- in parallel, a fast message (e.g. via Hyperlane) is sent to the pool on the destination
- the pool immediately gives funds to the user, minus a fee, because it knows that repayment is on the way
- when the bridge completes, the funds arrive in the proxy contract
- if the pool successfully filled the request, the funds are used to replenish the pool
- if not, then the funds are given to the user

### Format
What’s interesting about this pool design is that effectively 100% of volume is getting rebalanced over a bridge. At first glance, this might seem inefficient, because it’s not able to do “netting” of bi-directional flow, as seen in most pool-based bridges. But this is deliberate. The assumption is that if there’s any netting available, solvers will take it. And the only volume that will come to the pool is the “toxic” orderflow, i.e. the one-directional excess. This design embraces that reality and optimizes for it:

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
- one-sided pools
- rather than trying to manage connected pools on two or more chains, and rebalance between them, you can have simple one-sided pools
- there’s no need to manage how liquidity is allocated between pools on different chains, because each pool is isolated, and 100% of volume replenished back to the pool
- LPs choose exactly where to allocate liquidity, and get a simple deposit/withdraw UX
- you can still achieve multi-directional flow by deploying multiple (unrelated) pools on different chains, and composing them at the application layer
- permissionless deployment
- because pools are isolated, it’s much easier to let anyone deploy them
- this allows faster expansion to new chains
- yield maximization
- toxic orderflow tends to come in bursts, when solvers receive more demand than anticipated
- this means that liquidity is often idle, and can be deployed into other protocols to earn a “base yield” when it’s not in use
- this also pairs nicely with permissionless deployment, because you can have different pools with different risk / yield profiles
2 changes: 0 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,3 @@
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
31 changes: 31 additions & 0 deletions src/OpReceiverProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ contract OpReceiverProxy is ERC20, Utils {

// Public methods

/**
* @notice Deposit funds into the pool
*
* @param to Address to deposit on behalf of
*/
function deposit(address to) external payable {
uint256 shares = 0;
if (totalSupply() == 0) {
Expand All @@ -62,22 +67,43 @@ contract OpReceiverProxy is ERC20, Utils {
shares = (msg.value * totalSupply()) / address(this).balance;
}

// Mint shares
_mint(to, shares);
}

/**
* @notice Withdraw funds from the pool
*
* @param shares Amount of pool shares to withdraw
*/
function withdraw(uint256 shares) public {
uint256 amount = (address(this).balance * shares) / totalSupply();

// Burn shares
_burn(msg.sender, amount);

// Send the corresponding funds back to the user
_send(msg.sender, amount);
}

/**
* @notice Withdraw everything a particular user owns from the pool
*/
function withdrawAll() external {
withdraw(balanceOf(msg.sender));
}

// Restricted methods

/**
* @notice Hyperlane message-receiving hook
*
* @dev Only the Hyperlane Mailbox contract can call this method
*
* @param senderChainId The chain id where the message is originating from
* @param senderAddress The address of the sender on the origin chain
* @param data The message data
*/
function handle(
uint32 senderChainId,
bytes32 senderAddress,
Expand Down Expand Up @@ -114,6 +140,11 @@ contract OpReceiverProxy is ERC20, Utils {
}
}

/**
* @notice Fallback method
*
* @dev Only the predefined OP xDomain Messenger contract can call this method
*/
fallback() external payable {
// Only `OP_CROSS_DOMAIN_MESSENGER` is authorized to call this
if (msg.sender != OP_CROSS_DOMAIN_MESSENGER) {
Expand Down
22 changes: 20 additions & 2 deletions src/OpSenderProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,18 @@ contract OpSenderProxy {

// Public methods

function withdraw(address to, address opReceiverProxy) external payable {
/**
* @notice Withdraw funds to the base chain
*
* @param to Recipient address to receive the funds on the base chain
* @param opReceiverProxy The corresponding Relay pool on the base chain
*
* @return id The withdrawwal id
*/
function withdraw(
address to,
address opReceiverProxy
) external payable returns (uint256 id) {
// Associate the withdrawal to a unique id
uint256 id = nextId++;

Expand Down Expand Up @@ -58,6 +69,13 @@ contract OpSenderProxy {
// Trigger a canonical withdrawal to the `opReceiverProxy` contract
IOpCrossDomainMessenger(OP_CROSS_DOMAIN_MESSENGER).sendMessage{
value: amountLeftToBridge
}(opReceiverProxy, data, 200000);
}(
opReceiverProxy,
data,
// TODO: Is 200k enough for all base chains?
200000
);

return id;
}
}

0 comments on commit fe25f95

Please sign in to comment.