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, a new savings pool smart contract is deployed by the GoodGhosting team. Each game (also referred to as pool) runs for a fixed amount of time. There are always one or more deposit rounds - where you deposit a specific asset into the savings pool - followed by a single waiting round.
To win, players need to complete all deposits. Aka: they need to make a deposit into the pool smart contract every round, prior to the roundβs deadline. At the end of the game, all players can withdraw their initial deposit (minus any impermanent loss), irrespective whether they won or not.
Winners earn a slice of the poolβs rewards.This includes any interest generated through decentralized finance platforms as well as additional incentives and sponsorship (see Yield strategies). 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 pool 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 pool, are required to deposit the same ERC-20 token which is defined by inboundToken.
Deposit amounts
We support both pools with a fixed deposit amount (identical for each user), as well as pools 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 GoodGhosting pool. Once a users joins the pool with a specific deposit amount, all next deposits in that pool will have to be of the same amount. This is defined by depositAmount. For flexible pools there can be a maximum deposit limit per player, defined by maxFlexibleSegmentPaymentAmount.
Game duration
The duration of both the deposit and waiting round are determined at time of pool creation. This means each pool has a predictable fixed end date. Deposit rounds can have a different duration (segmentLength) than the final waiting round (waitingRoundSegmentLength), which enables different Pool Types with interesting dynamics.
Reward distributions are taking into account the total time (and amount) a user has funds deposited into a savings pool. Thereby increasing fairness to all depositors.
The sooner a player deposits in each segment, the bigger their share of the pool's generated interest and rewards in that segment - assuming they are considered winners at the end of the pool. Typically, a winning ulinkser (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 Yield strategiesto learn more about how rewards are generated.
In order to make the contracts modular, the contracts are divided into two types:
The pool 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 pool contract. Thereby only the pool contract can directly interact with the strategy contract, not players or any other external actors.
There are multiple yield strategy contracts available, but each pool contract can only be coupled to one strategy at a time. This is determined at the time the pool 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 pool 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 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 pool. If the game is still in the first segment, a user can still re-join the pool.
Admin functions
Our pool 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 pool, 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 pool 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
Can be called during or after a challenge:
lowerEarlyWithdrawFee() - Allows the admin to set a lower early withdrawal fee by specifying newEarlyWithdrawFee.
Can be called only before a challenge starts
setAdminFee(): Allows the admin to adjust the admin fee for managing the pool.
setEarlyWithdrawFee(): Allows the admin to set the early withdrawal fee, useful to discourage early withdrawals or adjust the penalty.
setPoolToken(): Allows the admin to set the challenge deposit token, useful when a different token is desired for participation.
setStrategy(): Allows the admin to set the investment strategy, useful to adjust the strategy based on market conditions.
setIncentiveToken() - Allows the admin to change the incentive token, useful when a different token is desired for rewards.
Can only be called after a challenge endsadminFeeWithdraw() - Allows the admin to withdraw the performance fee, if applicable. This function can be called only by the contract's admin and only after the challenge has ended.
Who is the admin?
Upon smart contract deployment, the admin is the HaloFi-owned address that deployed the contract to the blockchain. Upon initialization, the ownership typically gets transferred to a HaloFi-controlled Gnosis Safe multisig wallet. Example of such a wallet for Celo, Polygon and Base.