Sylan Staking
Single‑pool staking of SYL → SYL with per‑second emissions and standard MasterChef‑style accounting. Rewards can be minted by this contract (if it holds MINTER_ROLE on SylanToken) or pulled from an external rewardSource address (pre‑funded/approved). The contract is UUPS‑upgradeable and inherits pause/ownable guards from the shared base.
Time units use seconds (
block.timestamp). Amounts use 18‑decimals like SYL.
What it does
- Stake SYL and earn SYL at a configurable
rewardRatePerSecond. - Standard accounting with
accRewardPerShareand userrewardDebt. - User actions:
deposit(amount),withdraw(amount),harvest()(claim without changing stake). - Admin actions:
setRewardRatePerSecond(newRate),setRewardSource(src).
State (selected)
syl → IERC20— staking + reward token (SYL)rewardSource → address— if0x0, the contract mints rewards; otherwise it pulls fromrewardSource(requires allowance)rewardRatePerSecond → uint256— global emission ratelastUpdate → uint256— last timeaccRewardPerSharewas updatedaccRewardPerShare → uint256— cumulative rewards per staked token (fixed‑point)totalStaked → uint256User { amount, rewardDebt }— per user position
Accounting model (how rewards accrue)
Let Δ = now - lastUpdate. When anyone interacts:
- If
totalStaked > 0, accrue:
accRewardPerShare += Δ * rewardRatePerSecond * PRECISION / totalStaked - Set
lastUpdate = now. - A user’s pending =
user.amount * accRewardPerShare / PRECISION - user.rewardDebt.
On deposit(amount)
- Accrue, compute
pending, pay it to the user. - Pull
amountSYL from user, increasetotalStakedanduser.amount. - Set
user.rewardDebt = user.amount * accRewardPerShare / PRECISION. - Emits
Deposit(user, amount)andHarvest(user, pending)if any.
On withdraw(amount)
- Accrue, compute
pending, pay it. - Decrease
user.amountandtotalStaked, transferamountSYL back to user. - Update
rewardDebtas above. - Emits
Withdraw(user, amount)andHarvest(user, pending)if any.
On harvest()
- Accrue, compute
pending, pay it, updaterewardDebtto current position. - Emits
Harvest(user, pending).
PRECISIONis an internal fixed‑point scale factor (implementation detail). You don’t need it for integrations; rely on the view methods.
Reward funding
- If
rewardSource == address(0)→ this contract mints rewards to recipients usingSylanToken.mint(to, amount)(requires it to holdMINTER_ROLE). - Else → it transfers rewards using
safeTransferFrom(rewardSource, to, amount); the owner must keeprewardSourcesufficiently funded & approved.
When pulling, the contract emitsFundPulled(amount).
External interface
Views
syl() → addressrewardSource() → addressrewardRatePerSecond() → uint256lastUpdate() → uint256accRewardPerShare() → uint256totalStaked() → uint256
Writes (users)
deposit(uint256 amount)withdraw(uint256 amount)harvest()
Admin
setRewardRatePerSecond(uint256 newRate)setRewardSource(address src)
The contract inherits pause/unpause and UUPS upgrade controls from the base (owner‑gated).
Events
Deposit(address indexed user, uint256 amount)Withdraw(address indexed user, uint256 amount)Harvest(address indexed user, uint256 amount)RewardRateSet(uint256 oldRate, uint256 newRate)RewardSourceSet(address indexed source)FundPulled(uint256 amount)(emitted when rewards are pulled fromrewardSource)Upgraded(address implementation)/ContractUpgraded(address newImplementation, address caller, uint256 timestamp)(inherited)
Minimal ABI (client)
For typical staking UIs:
[
{"type":"function","stateMutability":"view","name":"syl","inputs":[],"outputs":[{"type":"address"}]},
{"type":"function","stateMutability":"view","name":"rewardRatePerSecond","inputs":[],"outputs":[{"type":"uint256"}]},
{"type":"function","stateMutability":"view","name":"totalStaked","inputs":[],"outputs":[{"type":"uint256"}]},
{"type":"function","stateMutability":"nonpayable","name":"deposit","inputs":[{"name":"amount","type":"uint256"}],"outputs":[]},
{"type":"function","stateMutability":"nonpayable","name":"withdraw","inputs":[{"name":"amount","type":"uint256"}],"outputs":[]},
{"type":"function","stateMutability":"nonpayable","name":"harvest","inputs":[],"outputs":[]}
]Integration tips
- Approve first: users must
approve(staking, amount)on SYL beforedeposit. - Pending UI: compute pending by reading
accRewardPerShare,totalStaked, and the user’samount/rewardDebt(or expose a helper in your SDK). - Claim‑only:
harvest()claims without changing stake; wire a “Claim” button. - APY display: convert
rewardRatePerSecond * 86400 * 365to yearly emissions and divide bytotalStakedfor a naive APR (document assumptions).
Admin & safety
- Keep
rewardRatePerSecondwithin treasury limits; raising it without funding will cause pull transfers to revert. - If using minting, grant
MINTER_ROLEto the staking contract only (not EOAs). Rotate via a multisig. - Pausing via the base contract will block writes; surface paused state in the UI.
Verification checklist
- Happy‑path tests: deposit → harvest → withdraw matches expected rewards.
- Edge tests: zero stake during accrual, multiple harvests, partial withdraws.
- Admin tests: changing
rewardRatePerSecondmid‑epoch, switchingrewardSource. - Security: reentrancy guards around transfers; SafeERC20 in use.
Out of scope for this page
- Node consensus rewards & slashing (see NodeRegistry)
- Token details (see Sylan Token)
- Addresses/ABIs (see Architecture → Addresses & ABIs)