A staking contract tracks each user's deposited amount and the time (or block) since their last update, then computes rewards proportionally when they claim or withdraw.
staked: public(HashMap[address, uint256])
reward_rate: public(uint256) # reward units per second per token
last_update: public(HashMap[address, uint256])
@external
def stake(amount: uint256):
self._update_rewards(msg.sender)
self.staked[msg.sender] += amount
ERC20(self.token).transferFrom(msg.sender, self, amount)
@internal
def _update_rewards(user: address):
elapsed: uint256 = block.timestamp - self.last_update[user]
self.rewards[user] += self.staked[user] * self.reward_rate * elapsed
self.last_update[user] = block.timestampAlways update rewards *before* changing a user's staked balance, otherwise you'll compute rewards using the wrong principal amount for part of the elapsed period.