We use game-design elements to reward users who hit their savings target (i.e. winners) more so than users who do not. This happens transparently on a public blockchain.
🕹️Game mechanics
For each game (also referred to as challenge or pool), a new savings challenge smart contract is deployed by the HaloFi team. Each challenge runs for a fixed amount of time. There are always one or more deposit rounds - where you deposit a specific digital asset into the savings challenge smart contract - followed by a single waiting round.
To win, players need to complete all deposits. Aka: they need to make a deposit into the challenge smart contract every round, prior to the round’s deadline. At the end of the challenge, all players can withdraw their initial deposit (minus any impermanent loss), irrespective whether they won or not.
Winners earn a slice of the challenge's rewards.This includes any interest generated through decentralized finance platforms as well as additional incentives and sponsorship (see Yield Strategies and HaloFi Challenges). The rewards are proportional to how much and when a user deposited. Typically, winnersearn more than when they would save by themselves.
The rewards are proportional to how much and when a user deposited. Typically, winnersearn more than when they would save by themselves.
⚙️ Technical
Deposit tokens
On a technical level, deposits take place inERC-20 tokens. These can be digital stablecoins (such as DAI, USDC or cUSD) whose value is soft-pegged to fiat currency (e.g. the US Dollar) and which can be transferred to our saving challenge smart contract, using blockchain technology. Additionally, we support most of the other ERC-20 tokens (including most volatile tokens) that are available on the Celo and Polygon blockchains.
Note that all users in a specific challenge, are required to deposit the same ERC-20 token which is defined by inboundToken.
Deposit amounts
We support both challenges with a fixed deposit amount (identical for each user), as well as challenges with flexible deposits (unique for each users).
When flexible deposits are enabled (flexibleSegmentPayment = true), everyone can decide how much they want to deposit into a HaloFi challenge. Once a users joins the challenge with a specific deposit amount, all next deposits in that challenge will have to be of the same amount. This is defined by depositAmount. For challenges with flexible (custom) despoit amounts, there can be a maximum deposit limit per player, defined by maxFlexibleSegmentPaymentAmount.
Challenge duration
The duration of both the deposit and waiting round are determined at time of challenge creation. This means each challenge has a predictable fixed end date. Deposit rounds can have a different duration (segmentLength) than the final waiting round (waitingRoundSegmentLength), which enables different with interesting dynamics.
Reward distributions are taking into account the total time (and amount) a user has funds deposited into a savings challenge. Thereby increasing fairness to all depositors.
The sooner a player deposits in each segment, the bigger their share of the challenge's generated interest and rewards in that segment - assuming they are considered winners at the end of the challenge. Typically, a winning user (who deposits during the very first block of a deposit segment) can earn up to a factor of 2 more the rewards compared to another user (who deposits during the very last block of a deposit segment). On the smart contract level, this is tracked using cumulativePlayerIndexSum and playerSharePercentage. This is updated for every new deposit for each segment. For the waiting round, rewards are calculated according to the total amount of funds a user has deposited (since each user starts this round at the same time).
See to learn more about how rewards are generated.
In order to make the contracts modular, the contracts are divided into two types:
The challenge contract that holds all the core game/pool logic (Pool.Sol)
It is the main contract through which players are able to make deposits into the underlying yield strategy contract and withdraw funds.
The yield strategycontracts that hold the logic to integrate with the external protocols.
The strategy contract is owned by the challenge contract. Thereby only the challenge contract can directly interact with the strategy contract, not players or any other external actors.
There are multiple yield strategy contracts available, but each challenge contract can only be coupled to one strategy at a time. This is determined at the time the challenge contract is deployed. Hence, the yield source becomes fixed at the time of pool deployment.
Flow of Funds
Public functions
Commonly used functions by users
The initial deposit happens by calling the joinGame()function on our challenge smart contract (Pool.sol). Subsequent deposit happen via the makeDeposit()function.
withdraw() - Allows player to withdraw their funds after the game ends. Losing players pay no fee and receive their initial deposit back (minus any IL if applicable). Winning players get their initial deposit back (minus any IL or performance fee, if applicable) and a share of the earned rewards, based on the cumulativePlayerIndexSum.
earlyWithdraw() - Allows a player to withdraw their funds before the game ends. An early withdrawal fee is charged, as defined in the constructor. IL is also taken into consideration (if applicable for the used yield strategy), prior to the user receiving the remaining portion of his funds. This function can be called any time prior to the end of the game, and results in the player exiting the savings challenge. If the game is still in the first segment, a user can still re-join the challenge.
Admin functions
Our challenge smart contract contain multiple administrative functions, that are only accessible to the admin (owner) of the contract.
Related to emergency scenarios 🚨
Certain admin functions are present, to allow to react to emergency scenarios. Typically these avoid user funds getting stuck in the strategy contract, or avoid further deposits. Find below more information about each of these administrative functions and how they impact and limit the game.
pause() - This pauses new deposits into the challenge, as well as early withdrawals. Specifically, the only function that still can be executed by users is withdraw(), after the game has ended. It does not result in the early completion of a game. Hence, if there are still deposits left to be made, those players will be considered a losing player. But at least it ensures that all players can withdraw their funds after the game has ended.
unpause() - This allows the admin to resume the game. It re-enables joinGame, makeDeposit and earlyWithdraw.
disableClaimingRewardTokens() - This disables the claiming of external reward tokens. This is useful when external reward contracts become inactive or reward balances aren't available, avoiding user funds getting stuck in the strategy and/or challenge contract.
enableEmergencyWithdraw() - This enables for the early completion of a game, in case of an emergency situation. Once called, it updates the last segment value to current segment and sets the emergency flag to true in the smart contract. This allows players to withdraw their funds (via the standard withdraw()), along with rewards for winners. Players will be considered winners if they have deposited either in the current and/or previous segment. Hence, it results in the early completion of an ongoing game, without forcing players to become 'losing' players.
General admin functions
adminFeeWithdraw() - Allows the admin to withdraw the performance fee, if applicable. This function can be called only once, and only by the contract's admin and only after the game has ended.
lowerEarlyWithdrawFee() - Allows the admin to set a lower early withdrawal fee by specifying newEarlyWithdrawFee.
setIncentiveToken - To add explanation
// To add remaining admin functions //
Who is the admin?
Upon initialization, the admin is the HaloFi-owned address (EOA) that deployed the contract to the blockchain. Soon after deployment, the ownership typically gets transferred to a HaloFi-controlled Safe multisig wallet. Example of such a wallet for Celo and Polygon.