A simple staking contract tracks each user's balance and the time of their last update, then accrues rewards as `amount * rate * elapsed`. Update the accrual *before* changing a balance, or a deposit would retroactively earn.
function _accrue(address u) internal {
Position storage p = positions[u];
rewards[u] += p.amount * rate * (block.timestamp - p.since) / 1e18;
p.since = uint64(block.timestamp);
}Keep the reward pool funded separately from principal so a withdrawal can never spend another user's stake.