Grant Application: ERC-20 Bribe marketplace for Fren Delegation

Introduction
This Grant Application is a proposal to build and open-source an ERC-20 bribe marketplace for Stakehouse LSD Node Operators to incentivize ETH deposits via Fren Delegation. Currently, there are no incentives for stakers to use Fren Delegation instead of the Giant Pools. This proposal would allow validators to deposit ERC-20 tokens into a smart contract where stakers who deposited via Fren Delegation may claim the tokens proportional to their deposit amount multiplied by a rewards ratio (total token rewards divided by 28). This proposal would benefit the validators who are waiting for ETH and stakers who are seeking greater rewards for their deposits. A Proof-of-Concept smart contract and dApp have already been deployed and successfully tested in Goerli testnet.

This application outlines a project scope, predicted impact, timeline, and required resources for the proposal. The community has expressed a positive outlook on this project in Discord, the governance forums, and on the Community Call.

Background
When stakers deposit via Fren Delegation or any other means of directly depositing ETH to a validator’s SavETH and MEVFees pools, they receive an LP token dstETH or ETHLP, which is unique to the validator they deposited to. Since these LP tokens are 1:1 with ETH, the balance of these LP tokens represents exactly how much ETH the staker deposited. dstETH and ETHLP tokens are burned when the staker withdraws their dETH.

Given a validator’s BLS key and a staker’s address, a Solidity smart contract can determine how much ETH the staker deposited into the validator’s pools. This ability makes possible a marketplace where validators can bribe stakers with token rewards.

dstETH
LP token received by stakers when they deposit ETH directly to a validator’s Protected Staking pool (SavETH).

ETHLP
LP token received by stakers when they deposit ETH directly to a validator’s MEVFees Pool.

BribeVault
BribeVault is the smart contract where validators deposit or withdraw ERC-20 tokens, and stakers claim rewards. The bribe contract makes several external calls to various Stakehouse contracts, including StakehouseUniverse, LiquidStakingManager, SavETH and MevFees pools, LP tokens dstETH and ETHLP, and SmartWallet.

High-level Overview
A node runner who holds any ERC-20 token, and is waiting for ETH before they can stake their validator, may deposit ERC-20 tokens into the BribeVault on the dApp to become incentivized. Each ERC-20 token deposit to the BribeVault requires the validator’s BLS key, a token contract address, and a total amount of tokens to deposit. Token deposits also require a depositor to approve the BribeVault to spend their tokens. Each incentivized validator appears in the dApp along with the rewards ratio and total token deposit amount. The rewards ratio is automatically calculated as total token rewards divided by 28.

A staker who wishes to earn more token rewards may deposit to an incentivized validator via Fren Delegation. After the incentivized validator has staked and minted derivative tokens, the staker may claim their token rewards on the dApp.

For example, if a validator deposits 28,000 USDT (1,000 USDT per 1 ETH) and a staker deposits 5 ETH via Fren Delegation, then after the validator mints derivatives and before the staker withdraws their dETH, the staker may claim 5,000 USDT from the bribe.

A validator may only have 1 active bribe at any given time, and each bribe is active for 1 year. A validator can deposit more of the same tokens to a bribe while it’s active and can withdraw unclaimed tokens after the bribe expires. Unclaimed tokens can only be withdrawn to the address the validator was registered with on Stakehouse LSD (the node runner’s address).

Implementation details

Checking LP token balances
To get the balance of the LP tokens dstETH and ETHLP, the BribeVault makes several external calls to various Stakehouse contracts.

  • stakeHouseKnotInfo(bytes) on StakehouseUniverse returns the “applicant” or Smart Wallet of a validator.
  • owner() of the Smart Wallet returns the LiquidStakingManager of the validator.
  • savETHVault() and stakingFundsVault() on the LiquidStakingManager returns the SavETH and MEVFees pool addresses of the validator.
  • lpTokenForKnot(bytes) on the SavETH and MEVFees pools returns the dstETH and ETHLP token addresses of the validator.
  • balanceOf(address) on the dstETH and ETHLP tokens returns the balance of the LP tokens for the validator held by the given address.

Node runner address from BLS key
To get the Node Runner’s address for a given BLS key (for withdrawing unclaimed token deposits), the BribeVault makes the following external calls to Stakehouse contracts:
stakeHouseKnotInfo(bytes) on StakehouseUniverse returns the “applicant” or Smart Wallet of a validator.
owner() of the Smart Wallet returns the LiquidStakingManager of the validator.
nodeRunnerOfSmartWallet(address) on the LiquidStakingManager returns the node runner’s address.

BribeVault public functions
constructor(address _feeRecipient, uint256 _feePerClaimDivisor)

  • Requires _feePerClaimDivisor be greater than the feePerClaimDivisorMin
  • Set feeRecipient to _feeRecipient
  • Set feePerClaimDivisor to _feePerClaimDivisor
  • Set owner to msg.sender
  • Emit VaultCreated
  • Emit FeeRecipientUpdated
  • Emit FeePerClaimUpdated

__

setFeeRecipient(address newFeeRecipient)

  • Requires newFeeRecipient does not equal existing feeRecipient
  • Emit FeeRecipientUpdated
  • Set feeRecipient to newFeeRecipient

__

setFeePerClaim(uint256 newFeePerClaimDivisor)

  • Requires newFeePerClaimDivisor is greater than or equal to existing feePerClaimDivisor
  • Requires newFeePerClaimDivisor is greater than or equal to feePerClaimDivisorMin
  • Emit FeePerClaimDIvisorUpdated
  • Set feePerClaimDivisor to newFeePerClaimDivisor

__

depositBribe(address bribeToken , uint256 bribeAmount , bytes calldata validatorBLSKey)

  • Requires bribeToken be a contract
  • Requires validatorBLSKey.length = 64
  • Requires msg.sender be the node runner for validatorBLSKey
  • Requires bribeAmount be greater than 0
  • If a bribe already exists for the validatorBLSKey, then
    – Require the bribeToken be the same as existing bribe token
    – Transfer ERC-20 tokens to contract (requires approval)
    – Increment tokenAmount on the TokenDeposit
    – Update the tokenToEthRatio on the TokenDeposit (total amount divided by 28)
    – Emit BribeToppedUp
  • If no bribe exists, then
    – Transfer ERC-20 tokens to contract (requires approval)
    – Add TokenDeposit to deposits mapping
    – Push new validator BLS key to the blsDepositKeys array
    –Increment the blsDepositKeyIndex and depositIndex variables
    – Emit BribeAdded
  • claim(bytes calldata validatorBLSKey)*
    • Requires the msg.sender hasn’t already claimed rewards for the bribe by checking claimedDeposits mapping for the bribe ID.
    • Calculate feeAmount (claimable amount divided by feePerClaimDivisor)
    • Requires msg.sender’s balance of LP dstETH and ETHLP tokens be greater than 0.
    • Requires the TokenDeposit tokenAmount be greater than or equal to the claim amount
    • Requires bribe expiration date be after the current block timestamp.
    • Claim amount is equal to (dstETH + ETHLP balances) * tokenEthRatio (for example, if a staker deposits all 24 ETH to Protected Staking and 4 ETH to MEV staking via Fren Delegation and the tokenEthRatio is 1000000, then the claim amount is 28000000, assuming the total bribe amount is 28000000 and tokenEthRatio is the total amount divided by 28.)
    • Decrement TokenDeposit tokenAmount for the bribe
    • Push bribe ID to claimedDeposits for msg.sender
    • Transfer tokens to msg.sender
    • Emit BribeClaimed
    • Send fee to feeRecipient if applicable
    • If bribe tokenAmount = 0, then delete Bribe and emit BribeRemoved

withdrawRemainingBribe(bytes calldata validatorBLSKey)

  • Requires the bribe expiration date be in the past and the bibe amount be greater than zero
  • Temporarily store the remaining token amount in a variable.
  • Delete the bribe’s TokenDeposit in deposits.
  • Emit BribeRemoved
  • Transfer the amount of remaining tokens to the validator owner (node runner)

Several helper functions:
- getSavETHandMEVFeesPoolsByBLS(bytes calldata validatorBLSKey)
- getLPTokensByBLS(bytes calldata validatorBLSKey)
- ethDepositsByBLSKeyAndAddress(bytes calldata validatorBLSKey, address depositor)
- claimable(bytes calldata validatorBLSKey)
- hasClaimed(bytes calldata validatorBLSKey, address recipient)
- getNodeRunnerAddress(bytes calldata validatorBLSKey)

BribeVault structures

struct TokenDeposit{
        uint256 id;
        address token;
        uint256 tokenAmount;
        uint256 tokenToEthRatio;
        uint256 expiration;
    }

BribeVault events and variables
event BribeAdded(uint256 indexed bribeId, address indexed sender, uint256 epoch);

  • event BribeRemoved(uint256 indexed bribeId, address indexed sender, uint256 epoch);*

  • event BribeToppedUp(uint256 indexed bribeId, address indexed sender, uint256 epoch);*

  • event BribeClaimed(uint256 indexed bribeId, address indexed sender, uint256 epoch);*

  • event FeeRecipientUpdated(address indexed sender, address newFeeRecipient, address oldFeeRecipient, uint256 epoch);*

  • event FeePerClaimUpdated(address indexed sender, uint256 newFeePerClaim, uint256 oldFeePerClaim, uint256 epoch);*

  • event VaultCreated(address indexed sender, uint256 epoch);*

  • mapping(bytes => TokenDeposit) public deposits; // Maps Validator BLS Key to (id,token,amount,ratio,expiration). Only 1 active bribe per validator*

  • bytes public blsDepositKeys;*

  • uint256 public blsDepositKeyIndex = 0;*

  • uint256 public depositIndex = 0;*

  • mapping(address => uint256) public claimedDeposits; // Maps recipient address to array of deposit.id*

  • address public stakehouseUniverse = 0xC38ee0eCc213293757dC5a30Cf253D3f40726E4c; // This is Goerli Stakehouse Universe *

  • uint256 public bribeLength = 31536000; // 365 days*

  • address public feeRecipient;*

  • uint256 public feePerClaim = 0; // not %, but a divisor. Example: 10000 tokens claim fee = 10000/5 if feePerClaim = 5*

  • address public owner;*

Caveats
Stakers must wait until the validator mints derivative tokens to claim their ERC-20 rewards. This is due to an obstacle in finding the LP token addresses given a validator BLS key that hasn’t yet been staked and minted.

The process of determining how much ETH a staker deposited by checking their balance of LP tokens dstETH and ETHLP relies on the staker to not transfer those tokens.

If an incentivized validator is funded by the Giant Pool, then the proportion of ERC-20 tokens in the bribe will be unclaimable. The node runner would need to withdraw those tokens upon expiration of the bribe.

Stakers must claim their bribe rewards before they withdraw their dETH. This is due to dstETH and ETHLP being burned when dETH is withdrawn.

Only a node runner may incentivize their own validator. Without this limitation, a bad actor could deploy a worthless token on mainnet and deposit those tokens for any validator to essentially deny that node runner access to incentivize themselves. This is because only 1 bribe may exist per validator at any time.

Grant Application

Applicant background
JohnBrown has over 5 years of experience in software development, and over 1 of those years was working with Ethereum and DeFi. The applicant has developed and deployed other dApps for token locking and distributions, direct depositing ETH to Stakehouse validators, and has extensive experience with web3 in Python and Javascript. The applicant may be contacted on Discord: JohnBrown#0339.

Project scope
The scope of this grant should be limited to open-source development and testing of the BribeVault contract and Bribe marketplace dApp frontend and backend.

The applicant is not responsible for hosting the dApp or deploying the BribeVault to mainnet. Anyone should be able to host the dApp and deploy the BribeVault since the code will be open source.

Objectives for BribeVault contract:

  • Allow node runners to incentivize their validators by approving and depositing ERC-20 tokens as bribes.
  • Allow stakers to claim ERC-20 rewards proportional to their dstETH and ETHLP balances multiplied by the bribe’s tokenToEth ratio.
  • Prevent stakers from claiming rewards more than once per bribe.
  • Allow node runners to deposit additional ERC-20 tokens into existing bribes.
  • Prevent validators from lowering the token to ETH rewards ratio after the initial deposit.
  • Allow validators to withdraw remaining tokens after a year from the original deposit.
  • Prevent contract creators from increasing feePerClaim
  • Prevent contract creators from setting very high feePerClaim
  • Documentation for deployment and interaction

Objectives for dApp:

  • Intuitive and user-friendly UI
  • Allow bribe depositors to approve BribeVault to spend ERC-20 token
  • Allow deposits of ERC-20 tokens to BribeVault
  • Enable stakers to claim ERC-20 tokens from bribes they’re eligible for
  • Allow withdrawing leftover ERC-20 tokens to node runners (after 1-year expiration)
  • Display claim fee to bribe depositors and claimers
  • Documentation for setup and use

Impact
There are currently 14 validators waiting for ETH from the Giant Pool or Fren Delegation before they can stake. 14 validators * 28 ETH = 392 ETH needed to fund the waiting validators. Since the Giant Pool is first come, first serve, most validators will be too slow to claim ETH from there. We’ve already seen bots claiming ETH the moment the Giant Pools have a sufficient amount.

If Node Runners could incentivize stakers to fund their validators via Fren Delegation with ERC-20 token bribes, then some validators who would otherwise be waiting months to stake can do so much faster. Similarly, stakers who would be waiting weeks or months for their Giant Pool deposits to be staked can deposit to an incentivized validator which should be staked sooner.

There’s an incentive for BSN buyers to join the ecosystem as stakers since BSN is the default bribe token. Furthermore, new arbitrage opportunities will exist when stakers are rewarded with dETH, sETH, or other ERC-20 tokens.

Timeline

Months since grant accepted - Objective

  • 1
    – BribeVault contract running in Goerli testnet
    – Code for BribeVault contract and dApp open-sourced on Github
    – BribeVault: depositBribe()
    – BribeVault: claim()
    – BribeVault: withdrawRemainingBribe()
    – dApp: Approve & Deposit ERC-20 tokens to BribeVault
    – dApp: Claim ERC-20 tokens from BribeVault
    – dApp: Withdraw expired bribe tokens to node runner
    – dApp: Top-up bribes with additional ERC-20

  • 2
    – UI/UX enhancements
    – Software quality assurance, stress testing, penetration testing
    – Documentation and deployment instructions for dApp and BribeVault

  • 3
    – Pull Request to Fren Delegation GitHub repository to add a list of BribeVault contracts and a new field on the UI to inform stakers if they’re depositing into an incentivized validator.

Conclusion

This LSD bribe marketplace based on Fren Delegation will provide a way for validators to become staked much quicker and for stakers to earn more for their ETH deposits. The Stakehouse LSD ecosystem would benefit from validators competing to reward stakers instead of racing to the Giant Pool. I recommend that the Grant Council issue 3 million tokens for the completion and open-sourcing of this project.

3 Likes

We have decided to approve your proposal and are prepared to award the agreed upon compensation of 3M BSN on completion!

1 Like

All deliverables have been completed and I am requesting that the Grants Council please issue 3 million tokens to my address: 0x2Bd94fa5beEfc4b960BAe01937088ED9669C1f1D

1 Like

https://www.etherstake.house/ is the Goerli bribe dapp. Could you also share github link, please.

GIthub repo: GitHub - vdallco/Liquid-Staking-Bribes: ERC-20 token bribes for LSD Network Owners and Node Runners of Blockswap's Stakehouse liquid-staking derivatives platform.

1 Like