-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add estimated rewards calculation to the API.
- Loading branch information
Jiri Malek
committed
Apr 30, 2020
1 parent
b911f92
commit ba0bbe4
Showing
11 changed files
with
434 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package resolvers | ||
|
||
import ( | ||
"fantom-api-graphql/internal/repository" | ||
"fantom-api-graphql/internal/types" | ||
"fmt" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
"math/big" | ||
) | ||
|
||
// constants used in reward calculations | ||
const ( | ||
erwSecondsInDay uint64 = 86400 | ||
erwSecondsInWeek uint64 = 86400 * 7 | ||
erwSecondsInYear uint64 = 31556926 | ||
erwSecondsInMonth = erwSecondsInYear / 12 | ||
) | ||
|
||
// EstimatedRewards represents resolvable estimated rewards structure. | ||
type EstimatedRewards struct { | ||
repo repository.Repository | ||
Staked hexutil.Uint64 | ||
TotalStaked hexutil.Big | ||
LastEpoch types.Epoch | ||
} | ||
|
||
// NewEstimatedRewards builds new resolvable estimated rewards structure. | ||
func NewEstimatedRewards(ep *types.Epoch, amount *hexutil.Uint64, total *hexutil.Big, repo repository.Repository) EstimatedRewards { | ||
return EstimatedRewards{ | ||
repo: repo, | ||
Staked: *amount, | ||
TotalStaked: *total, | ||
LastEpoch: *ep, | ||
} | ||
} | ||
|
||
// estimateRewardsByAddress instantiates the estimated rewards for specified address if possible. | ||
func (rs *rootResolver) estimateRewardsByAddress(addr *common.Address, ep *types.Epoch, total *hexutil.Big) (EstimatedRewards, error) { | ||
// try to get the address involved | ||
acc, err := rs.repo.Account(addr) | ||
if err != nil { | ||
// log issue and return empty data | ||
rs.log.Error("invalid address or address not found") | ||
return EstimatedRewards{}, fmt.Errorf("address not found") | ||
} | ||
|
||
// inform to debug | ||
rs.log.Debugf("calculating rewards estimation for address [%s]", acc.Address.String()) | ||
|
||
// get the address balance | ||
balance, err := rs.repo.AccountBalance(acc) | ||
if err != nil { | ||
// log issue and return empty data | ||
rs.log.Errorf("can not get balance for address [%s]", acc.Address.String()) | ||
return EstimatedRewards{}, fmt.Errorf("address balance not found") | ||
} | ||
|
||
// get the value of the balance as Uint64 value | ||
val := hexutil.Uint64(new(big.Int).Div(balance.ToInt(), new(big.Int).SetUint64(1000000000000000000)).Uint64()) | ||
return NewEstimatedRewards(ep, &val, total, rs.repo), nil | ||
} | ||
|
||
// EstimateRewards resolves reward estimation for the given address or amount staked. | ||
func (rs *rootResolver) EstimateRewards(args *struct { | ||
Address *common.Address | ||
Amount *hexutil.Uint64 | ||
}) (EstimatedRewards, error) { | ||
// at least one of the parameters must be present | ||
if args == nil || (args.Address == nil && args.Amount == nil) { | ||
// log issue and return empty data | ||
rs.log.Error("can not calculate estimated rewards without parameters") | ||
return EstimatedRewards{}, fmt.Errorf("missing both address and amount") | ||
} | ||
|
||
// get the latest sealed epoch | ||
// the data could be delayed behind the real-time sealed epoch due to caching, | ||
// but we don't need that precise reflection here | ||
ep, err := rs.repo.CurrentSealedEpoch() | ||
if err != nil { | ||
// log issue and return empty data | ||
rs.log.Errorf("can not get the current sealed epoch information; %s", err.Error()) | ||
return EstimatedRewards{}, fmt.Errorf("current sealed epoch not found") | ||
} | ||
|
||
// get the current total staked amount | ||
total, err := rs.repo.TotalStaked() | ||
if err != nil { | ||
// log issue and return empty data | ||
rs.log.Errorf("can not get the current total staked amount; %s", err.Error()) | ||
return EstimatedRewards{}, fmt.Errorf("current total staked amount not found") | ||
} | ||
|
||
// if address is specified, pull the estimation from it | ||
if args.Address != nil { | ||
return rs.estimateRewardsByAddress(args.Address, ep, total) | ||
} | ||
|
||
// get the value directly from the provided amount | ||
return NewEstimatedRewards(ep, args.Amount, total, rs.repo), nil | ||
} | ||
|
||
// canCalculateRewards checks if the reward can actually be calculated | ||
func (erw EstimatedRewards) canCalculateRewards() bool { | ||
zero := new(big.Int) | ||
return erw.Staked > 0 && | ||
erw.LastEpoch.BaseRewardPerSecond.ToInt().Cmp(zero) > 0 && | ||
erw.TotalStaked.ToInt().Cmp(zero) > 0 | ||
} | ||
|
||
// getRewards calculates the rewards value for the given time period. | ||
func (erw EstimatedRewards) getRewards(period uint64) hexutil.Big { | ||
// validate that we can actually calculate the value | ||
if !erw.canCalculateRewards() { | ||
fmt.Printf("can not calculate!") | ||
return hexutil.Big{} | ||
} | ||
|
||
// prep values and calculate results | ||
// (perSecond * period * stakedAmount) / totalStakedAmount | ||
base := new(big.Int).Mul(erw.LastEpoch.BaseRewardPerSecond.ToInt(), new(big.Int).SetUint64(period)) | ||
staked := new(big.Int).Mul(base, new(big.Int).SetUint64(uint64(erw.Staked))) | ||
val := new(big.Int).Div(staked, erw.TotalStaked.ToInt()) | ||
|
||
// return the value | ||
return hexutil.Big(*val) | ||
} | ||
|
||
// DailyReward calculates daily rewards for the given rewards estimation. | ||
func (erw EstimatedRewards) DailyReward() hexutil.Big { | ||
return erw.getRewards(erwSecondsInDay) | ||
} | ||
|
||
// WeeklyReward calculates daily rewards for the given rewards estimation. | ||
func (erw EstimatedRewards) WeeklyReward() hexutil.Big { | ||
return erw.getRewards(erwSecondsInWeek) | ||
} | ||
|
||
// MonthlyReward calculates daily rewards for the given rewards estimation. | ||
func (erw EstimatedRewards) MonthlyReward() hexutil.Big { | ||
return erw.getRewards(erwSecondsInMonth) | ||
} | ||
|
||
// YearlyReward calculates daily rewards for the given rewards estimation. | ||
func (erw EstimatedRewards) YearlyReward() hexutil.Big { | ||
return erw.getRewards(erwSecondsInYear) | ||
} | ||
|
||
// CurrentRewardRateYearly calculates average reward rate | ||
// for any staked amount in average per year | ||
func (erw EstimatedRewards) CurrentRewardRateYearly() int32 { | ||
// validate that we can actually calculate the value | ||
if !erw.canCalculateRewards() { | ||
return 0 | ||
} | ||
|
||
// prep and calculate the rate | ||
base := new(big.Int).Mul(erw.LastEpoch.BaseRewardPerSecond.ToInt(), new(big.Int).SetUint64(erwSecondsInYear)) | ||
toPct := new(big.Int).Mul(base, new(big.Int).SetUint64(100)) | ||
val := new(big.Int).Div(toPct, erw.TotalStaked.ToInt()) | ||
|
||
// get the value | ||
return int32(val.Int64()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 23 additions & 3 deletions
26
internal/graphql/schema/definition/types/estimated_rewards.graphql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package cache | ||
|
||
import ( | ||
"fantom-api-graphql/internal/types" | ||
"fmt" | ||
) | ||
|
||
// lastEpochCacheKey represents the in-memory cache key for the latest sealed Epoch data. | ||
const lastEpochCacheKey = "last-sealed-epoch" | ||
|
||
// PullLastEpoch extracts information about the latest Epoch from the in-memory cache if available. | ||
func (b *MemBridge) PullLastEpoch() *types.Epoch { | ||
// try to get the Epoch data from the cache | ||
data, err := b.cache.Get(lastEpochCacheKey) | ||
if err != nil { | ||
// cache returns ErrEntryNotFound if the key does not exist | ||
return nil | ||
} | ||
|
||
// do we have the data? | ||
ep, err := types.UnmarshalEpoch(data) | ||
if err != nil { | ||
b.log.Criticalf("can not decode epoch data from in-memory cache; %s", err.Error()) | ||
return nil | ||
} | ||
|
||
return ep | ||
} | ||
|
||
// PushLastEpoch stores provided latest sealed Epoch in the in-memory cache. | ||
func (b *MemBridge) PushLastEpoch(ep *types.Epoch) error { | ||
// we need valid account | ||
if nil == ep { | ||
return fmt.Errorf("undefined epoch can not be pushed to the in-memory cache") | ||
} | ||
|
||
// encode account | ||
data, err := ep.Marshal() | ||
if err != nil { | ||
b.log.Criticalf("can not marshal epoch to JSON; %s", err.Error()) | ||
return err | ||
} | ||
|
||
// set the data to cache by block number | ||
return b.cache.Set(lastEpochCacheKey, data) | ||
} |
Oops, something went wrong.