diff --git a/x/cosmostest/math/vesting.go b/x/cosmostest/math/vesting.go new file mode 100644 index 0000000..e9af00f --- /dev/null +++ b/x/cosmostest/math/vesting.go @@ -0,0 +1,47 @@ +package math + +import ( + "math/big" + "time" +) + +// Calculate amount due to provider assuming linear payout schedule. +// +// Does NOT account for anything out-of-bounds. This must be checked beforehand. +func CalcAmountVestableLinear( + total *big.Int, + remaining *big.Int, + date time.Time, + beginDate time.Time, + endDate time.Time, +) (*big.Int, error) { + timePassed := big.NewInt(date.Unix() - beginDate.Unix()) + totalTime := big.NewInt(endDate.Unix() - beginDate.Unix()) + + taken := big.NewInt(0) + taken.Sub(total, remaining) + + out := big.NewInt(0) + out.Mul(total, timePassed) + out.Div(out, totalTime) + out.Sub(out, taken) + + return out, nil +} + +// No longer using exponential - use linear distribution schedule +// because exponential rewards are a weak incentive for providers + +// func CalcAmountVestableExponential( +// ctx sdk.Context, +// total big.Int, +// remaining big.Int, +// date time.Time, +// beginDate time.Time, +// endDate time.Time, +// ) big.Int { +// timescale := big.NewInt(endDate.Unix() - beginDate.Unix()) +// timePassed := big.NewInt(date.Unix() - beginDate.Unix()) +// expTerm := big.NewInt(1) +// expTerm.Exp(big.NewInt(2), timePassed, big.NewInt(1)) +// } diff --git a/x/cosmostest/math/vesting_test.go b/x/cosmostest/math/vesting_test.go new file mode 100644 index 0000000..b287774 --- /dev/null +++ b/x/cosmostest/math/vesting_test.go @@ -0,0 +1,115 @@ +package math + +import ( + "fmt" + "math/big" + "testing" + "time" +) + +func TestLinearVesting(t *testing.T) { + + // Total: 1000 tokens + // Time: halfway through + // Claimed: 0 + // Expected: 500 + if res, err := CalcAmountVestableLinear( + big.NewInt(1000), + big.NewInt(1000), + time.Now(), + time.Unix(time.Now().Unix()-1000, 0), + time.Unix(time.Now().Unix()+1000, 0), + ); err != nil { + panic(err) + } else { + expectEq(res, 500) + } + + // Total: 1000 tokens + // Time: beginning through + // Claimed: 0 + // Expected: 0 + if res, err := CalcAmountVestableLinear( + big.NewInt(1000), + big.NewInt(1000), + time.Now(), + time.Now(), + time.Unix(time.Now().Unix()+1000, 0), + ); err != nil { + panic(err) + } else { + expectEq(res, 0) + } + + // Total: 1000 tokens + // Time: end + // Claimed: 0 + // Expected: 1000 + if res, err := CalcAmountVestableLinear( + big.NewInt(1000), + big.NewInt(1000), + time.Now(), + time.Unix(time.Now().Unix()-1000, 0), + time.Now(), + ); err != nil { + panic(err) + } else { + expectEq(res, 1000) + } + + // Total: 1000 tokens + // Time: end + // Claimed: 1000 + // Expected: 1000 + if res, err := CalcAmountVestableLinear( + big.NewInt(1000), + big.NewInt(0), + time.Now(), + time.Unix(time.Now().Unix()-1000, 0), + time.Now(), + ); err != nil { + panic(err) + } else { + expectEq(res, 0) + } + + // Total: 1000 tokens + // Time: 33% + // Claimed: 250 (25%) + // Expected: 333-250 = 83 + if res, err := CalcAmountVestableLinear( + big.NewInt(1000), + big.NewInt(750), + time.Now(), + time.Unix(time.Now().Unix()-1000, 0), + time.Unix(time.Now().Unix()+2000, 0), + ); err != nil { + panic(err) + } else { + expectEq(res, 83) + } + + // Total: 1000 tokens + // Time: 66% + // Claimed: 250 (25%) + // Expected: 666-250 = 416 + if res, err := CalcAmountVestableLinear( + big.NewInt(1000), + big.NewInt(750), + time.Now(), + time.Unix(time.Now().Unix()-2000, 0), + time.Unix(time.Now().Unix()+1000, 0), + ); err != nil { + panic(err) + } else { + expectEq(res, 416) + } + +} + +func expectEq(a *big.Int, b int) { + a2 := int(a.Uint64()) + if a2 != b { + panic(fmt.Sprintf("expected %d, got %d", b, a2)) + } +}