cd eth-staking-fee-distributor-contracts
yarn
cp .env.example .env
# edit .env with the actual values
yarn typechain
curl -L https://foundry.paradigm.xyz | bash
source /Users/$USER/.bashrc
foundryup
yarn test
The basic use case is reflected in ./test/foundry/Integration.t.sol
's test_Main_Use_Case
function.
-
Anyone (deployer, does not matter who) deploys
FeeDistributorFactory
providing_defaultClientBasisPoints
argument. This default value of client basis points will be used in case later on, during a client instance creation, increateFeeDistributor
function,_clientConfig.basisPoints == 0
. Initially, the plan is to use9000
as the default value. -
Anyone (deployer, does not matter who) deploys
Oracle
. -
Anyone (deployer, does not matter who) deploys
P2pOrgUnlimitedEthDepositor
providing arguments:_mainnet
: true (means "mainnet")_feeDistributorFactory
: address ofFeeDistributorFactory
-
The deployer calls
setP2pEth2Depositor
onFeeDistributorFactory
with the address of theP2pOrgUnlimitedEthDepositor
contract from Step 3. -
Anyone (deployer, does not matter who) deploys reference implementations of FeeDistributor contracts. Currently, there are 3 of them:
ElOnlyFeeDistributor
arguments:- address of
FeeDistributorFactory
from Step 1. - address of the service (P2P) fee recipient
- address of
OracleFeeDistributor
arguments:- address of
Oracle
from Step 2. - address of
FeeDistributorFactory
from Step 1. - address of the service (P2P) fee recipient
- address of
ContractWcFeeDistributor
arguments:- address of
FeeDistributorFactory
from Step 1. - address of the service (P2P) fee recipient
- address of
-
The deployer calls
transferOwnership
onFeeDistributorFactory
with the secure P2P address as an argument. -
The owner calls
changeOperator
onFeeDistributorFactory
with the address of the operator. The operator is an Ethereum account who can callcreateFeeDistributor
for each new client/FeeDistributor type/FeeDistributor percentages. The operator can be a hot wallet, less secure than the owner. Since the primary way to createFeeDistributor
instances isP2pOrgUnlimitedEthDepositor
'saddEth
, the operator is needed only for creating alternativeFeeDistributor
instances when either client or referrer configs need to be updated. -
The deployer does the same steps (6 and 7) for
Oracle
. -
A client calls
addEth
onP2pOrgUnlimitedEthDepositor
sending (32 * validator count) ETH and providing the arguments:_referenceFeeDistributor
: address of FeeDistributor template that determines the terms of staking service_clientConfig
address and basis points (percent * 100) of the client_referrerConfig
address and basis points (percent * 100) of the referrer.
The client's ETH is now held in the P2pOrgUnlimitedEthDepositor
contract. A new FeeDistributor
instance has been deployed if it didn't exist for the same values of
_referenceFeeDistributor
, _clientConfig
, and _referrerConfig
. If it did exist, no additional contract would be deployed.
Instead, the newly deposited ETH value would be added to the stored depositAmount
value corresponding to the FeeDistributor
instance address.
- P2P service listens to the
P2pOrgUnlimitedEthDepositor__ClientEthAdded
events emitted onaddEth
execution. If it determines that a certainFeeDistributor
instance has at least 32 ETH, it callsmakeBeaconDeposit
onP2pOrgUnlimitedEthDepositor
providing the arguments:_feeDistributorInstance
: user FeeDistributor instance that determines the terms of staking service (from Step 9)_pubkeys
: BLS12-381 public keys_signatures
: BLS12-381 signatures_depositDataRoots
: SHA-256 hashes of the SSZ-encoded DepositData objects
As a result, multiple ETH2 deposits are made.
-
P2P service sets the
_newFeeDistributorAddress
from Step 9 as the EL rewards recipient in validators' settings. Now the per client/terms of service copy ofFeeDistributor
contract will be receiving EL rewards (MEV, priority fees). -
(Periodically, e.g. daily) P2P oracle service fetches the latest CL rewards sums for each
OracleFeeDistributor
instance's validators. This data is used as theoracleData
argument for./scripts/buildMerkleTreeForFeeDistributorAddress.ts
function. The result is a Merkle Tree. -
The operator calls
report
onOracle
providing the Merkle Root as the argument. -
Anyone (client, P2P withdrawer service, does not matter who) calls
./scripts/obtainProof.ts
function providing the address of theOracleFeeDistributor
instance as the argument. It returns:
proof
- Merkle proof (the leaf's sibling, and each non-leaf hash that could not otherwise be calculated without additional leaf nodes)_amountInGwei
- sum of CL rewards for all validators corresponding to theOracleFeeDistributor
instance.
- Anyone at any time can call
withdraw
on a per client/terms of service copy ofFeeDistributor
. ForOracleFeeDistributor
the required arguments are:_proof
and_amountInGwei
from Step 14. ForElOnlyFeeDistributor
andContractWcFeeDistributor
no arguments are needed.
The result will be sending the whole current contract's balance to - address of the service (P2P) - address of the client - address of the referrer (optional)
proportionally to the pre-defined - % of EL rewards that should go to the service (P2P) - % of EL rewards that should go to the client - % of EL rewards that should go to the referrer (optional)
FeeDistributorFactory deploys new FeeDistributor
instances based on provided FeeDistributor
template (reference instance) that determines the type and values:
_clientConfig
: address and basis points (percent * 100) of the client_referrerConfig
: address and basis points (percent * 100) of the referrer that determine percentages of rewards that will go to the client, the service, and the referrer (terms of service).
Its createFeeDistributor
function can be called by either P2pOrgUnlimitedEthDepositor
contract during addEth
transaction
or the P2P operator if the validators already exist.
FeeDistributor stores
- address of
FeeDistributorFactory
- address of the service (P2P) fee recipient
- address and basis points of EL rewards that should go to the client
- address and basis points of EL rewards that should go to the referrer (optional)
FeeDistributor
contract instance is unique and has its own address if all of these are the same:
_referenceFeeDistributor
: address of the reference implementation of FeeDistributor used as the basis for clones_clientConfig
: address and basis points (percent * 100) of the client_referrerConfig
: address and basis points (percent * 100) of the referrer
FeeDistributor
contract's address is assigned in a validator's setting as EL rewards recipient. Thus, its balance increases over time with each EL reward.
Anyone at any time can call withdraw
on the user's own copy of FeeDistributor
(See Step 15 above).
Also, it's possible for the P2P operator and the client to call withdraw
as an ERC-4337 UserOperation.
An example of how to do it
- for
ElOnlyFeeDistributor
andContractWcFeeDistributor
can be found at./scripts/sendErc4337UserOperation.ts
- for
OracleFeeDistributor
can be found at./scripts/sendErc4337UserOperationForOracleFeeDistributor.ts
UserOperation will allow the P2P operator and the client not to pay for the gas. Gas fee will be deducted from theFeeDistributor
instance itself.FeeDistributor
is an ERC-4337Sender
but it is restricted towithdraw
function only.
Currently, there are 3 types of FeeDistributor
:
ElOnlyFeeDistributor
accepting and splitting EL rewards onlyOracleFeeDistributor
accepting EL rewards only but splitting them with consideration of CL rewardsContractWcFeeDistributor
accepting and splitting both CL and EL rewards
New types can be added in the future.
Clients can implement and deploy their own type of FeeDistributor
and then use it as a _referenceFeeDistributor
for addEth
function in P2pOrgUnlimitedEthDepositor
.
P2P will then be able to evaluate the proposed terms of service.
If they are OK, P2P will proceed with makeBeaconDeposit
.
If they are not, the client will be able to return their ETH from P2pOrgUnlimitedEthDepositor
via the refund
function.
voluntaryExit
function of FeeDistributor
allows the client to signal P2P about their intention to exit 1 or more validators that correspond to the provided _pubkeys
.
In ContractWcFeeDistributor
it has additional functionality for accounting 32 ETH per validator (collateral) that should not be split and should go to the client in full.
Oracle stores Merkle Root, which is used to verify data (FeeDistributor
instance address, _amountInGwei) using Merkle Proof.
P2pOrgUnlimitedEthDepositor is a batch deposit contract.
Its makeBeaconDeposit
function passes client's ether to the official ETH2 DepositContract and calls FeeDistributorFactory
to create an instance of FeeDistributor
.
Its refund
function allows the client to get their ETH back if P2P did not use it for staking during the pre-defined TIMEOUT
.
Its rejectService
function allows P2P to indicate that there will be no Beacon deposit made so the client can get a refund immediately without waiting for expiration.
Can be helpful if the client made a mistake and asked P2P to release their funds sooner than expiration. Or if P2P does not agree with the terms of service suggested by the client.