Skip to content

Repository files navigation

RedDuck

RedDuck Staking

A tokenized staking vault, built from scratch and shipped twice:
once in Solidity for EVM, once in Rust for Solana.


What this is

A staking vault that takes a deposit of one token and issues shares back, redeemable later for a proportional slice of whatever the vault holds — the standard tokenized-vault shape, implemented independently on two chains, plus the dApp that drives both:

  • EVM (apps/evm/) — a Solidity vault with ERC-4626-style share accounting, written directly against ERC20/IERC4626 rather than inheriting OpenZeppelin's vault.
  • Solana (apps/solana/) — an Anchor program with the same accounting rules expressed against Solana's account model instead of Solidity storage.
  • Frontend (apps/frontend/) — the React dApp that drives both vaults side by side.

Both sides track deposited assets in their own internal counter rather than reading a token balance directly, so a plain transfer into the vault (a "donation") can't be used to distort the exchange rate — the classic first-depositor inflation attack against naive vault implementations. Both also require a caller-supplied minimum-output on deposit and redeem, so a transaction reverts instead of executing at a worse rate than the caller expects.

EVM

Foundry project in apps/evm/.

Contract Role
StakingVault.sol The vault itself: deposit/mint/withdraw/redeem, plus ERC-5143 overloads that take a minShares/maxAssets/minAssets bound and revert with SLIPPAGE if it's violated. Tracks _totalAssets internally rather than trusting token.balanceOf(this).
StakingVaultETHGateway.sol Lets a user stake/unstake plain ETH; wraps to WETH and back around a StakingVault deployed against a WETH asset.
WETH9.sol Canonical wrap/unwrap ERC20, used as the vault's asset when staking through the ETH gateway.
DemoToken.sol 18-decimal ERC20 with a public mint(), used as the vault's asset on the token-staking path — a Sepolia faucet substitute

Share pricing uses a virtual-offset formula (VIRTUAL_SHARES = 1e6, VIRTUAL_ASSETS = 1) rather than a raw ratio, so the first depositor can't be front-run into a worthless share price.

cd apps/evm
forge build
forge test

Solana

Anchor program in apps/solana/, workspace member staking_vault.

Module Role
state.rs VaultState PDA — mint addresses, vault token account, total_assets, bump.
math.rs convert_to_shares/convert_to_assets, the same virtual-offset formula as the EVM vault (VIRTUAL_SHARES = 1_000_000, VIRTUAL_ASSETS = 1)
instructions/ initialize, stake, unstake, donate (inflation-attack test fixture), faucet (devnet-only underlying-mint top-up).

VaultState, the share mint, and the vault's token account are all PDAs derived from (program id, underlying mint) — seeds vault, share_mint, vault_token in constants.rs

cd apps/solana
yarn install
anchor build
anchor test

Frontend

React + Vite dApp in apps/frontend/ — stake/unstake against both vaults side by side, wallet connect for EVM and Solana, live vault stats. See apps/frontend/README.md for the file-by-file breakdown.

cd apps/frontend
npm install
cp .env.example .env.local   # fill in the vault/token addresses from your deployment
npm run dev

License

MIT © RedDuck Limited

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages