This repository implements a mechanism to distribute rewards vested in a DssVest contract on L1 to users staking tokens in a StakingRewards farm on Arbitrum. It uses the Arbitrum Token Bridge to transfer the rewards from L1 to L2.
L1FarmProxy.sol
- Proxy to the farm on the L1 side. Receives the token reward (expected to come from aVestedRewardDistribution
contract) and transfers it cross-chain to theL2FarmProxy
. An instance ofL1FarmProxy
must be deployed for each supported pair of staking and rewards token.L2FarmProxy.sol
- Proxy to the farm on the L2 side. Receives the token reward (expected to be bridged from theL1FarmProxy
) and forwards it to the StakingRewards farm where it gets distributed to stakers. An instance ofL2FarmProxy
must be deployed for each supported pair of staking and rewards token.EtherForwader.sol
- A simple ether forwarding contract deployed on L2 to collect excess fee refunds and forward those to theL2GovernanceRelay
.
- The L2 staking tokens and the L1 and L2 rewards tokens are not provided as part of this repository. It is assumed that only simple, regular ERC20 tokens will be used. In particular, the supported tokens are assumed to revert on failure (instead of returning false) and do not execute any hook on transfer.
DssVest
is used to vest the rewards token on L1.VestedRewardDistribution
is used to vest the rewards tokens fromDssVest
, transfer them to theL1FarmProxy
and trigger the bridging of the tokens.- The Arbitrum Token Bridge is used to bridge the tokens from L1 to L2.
- The escrow contract is used by the Arbitrum Token Bridge to hold the bridged tokens on L1.
StakingRewards
is used to distribute the bridged rewards to stakers on L2.- The
L1GovernanceRelay
&L2GovernanceRelay
allow governance to exert admin control over the deployed L2 contracts. These contracts have been previously deployed to control the Arbitrum Dai Bridge.
- It is expected that the ether balance of the
L1FarmProxy
is continuously monitored and topped up as needed to ensure the successful operation of the proxy. - Once the vested amount of rewards tokens exceeds
L1FarmProxy.rewardThreshold
, a keeper callsVestedRewardDistribution.distribute()
to vest the rewards and have them bridged to L2. - Once the bridged amount of rewards tokens exceeds
L2FarmProxy.rewardThreshold
, anyone (e.g. a keeper or an L2 staker) can callL2FarmProxy.forwardReward()
to distribute the rewards to the L2 farm.
Note that L1FarmProxy.rewardThreshold
must be sufficiently large to reduce the frequency of cross-chain transfers (thereby also reducing the amount of ether that needs to be provisionned into the L1FarmProxy
). L2FarmProxy.rewardThreshold
must also be sufficiently large to limit the reduction of the farm's rate of rewards distribution. Consider also choosing L2FarmProxy.rewardThreshold <= L1FarmProxy.rewardThreshold
so that the bridged rewards can be promptly distributed to the farm. In the initialization library, these two variables are assigned the same value.
Add the required env variables listed in .env.example
to your .env
file, and run source .env
.
Make sure to set the L1
and L2
env variables according to your desired deployment environment.
Mainnet deployment:
L1=mainnet
L2=arbitrum_one
Testnet deployment:
L1=sepolia
L2=arbitrum_one_sepolia
The deployment assumes that the arbitrum-token-bridge has already been deployed and was properly initialized.
Fill in the addresses of the L2 staking token and L1 and L2 rewards tokens in script/input/{chainId}/config.json
under the "stakingToken"
and "rewardsToken"
keys. It is assumed that these tokens have been registered with the Arbitrum Token Bridge.
Fill in the address of the mainnet DssVest contract in script/input/1/config.json
under the vest
key. It is assumed that the vesting contract was properly initialized. On testnet, a mock DssVest contract will automatically be deployed.
Start by deploying the EtherForwarder
and L2FarmProxySpell
singletons. You must use a deployment key for which the current nonce on L2 has been "burned" on L1 (i.e. has already been spent on L1 in a transaction that is not a contract creation transaction). This is required to make sure the address of the EtherForwarder
can never contain code on L1. If that address ever had code on L1, it would no longer be usable as an excess fee refund receiver (see the reason why here).
forge script script/DeploySingletons.s.sol:DeploySingletons --slow --multi --broadcast --verify
Next, run the following command to deploy the L1 vested rewards distribution contract, the L2 farm and the L1 and L2 proxies:
forge script script/DeployProxy.s.sol:DeployProxy --slow --multi --broadcast --verify
On mainnet, the farm proxies should be initialized via the spell process. To determine an adequate value for the maxGas
storage variable of L1FarmProxy
, the Estimate
script can be run:
forge script script/Estimate.s.sol:Estimate
On testnet, the proxies initialization can be performed via the following command:
forge script script/Init.s.sol:Init --slow --multi --broadcast
Run the following command to distribute the vested funds to the L1 proxy:
forge script script/Distribute.s.sol:Distribute --slow --multi --broadcast
Wait for the transaction to be relayed to L2, then run the following command to forward the bridged funds from the L2 proxy to the farm:
forge script script/Forward.s.sol:Forward --slow --multi --broadcast