Skip to content

Commit

Permalink
feat: calculator prototype (#288)
Browse files Browse the repository at this point in the history
  • Loading branch information
sepehr-dh99 authored Jan 22, 2025
1 parent 9a3c89d commit e80bc22
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 64 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ gen:
go run ./internal/generator/main.go \
"./internal/engine/command/crowdfund/crowdfund.yml" \
"./internal/engine/command/voucher/voucher.yml" \
"./internal/engine/command/market/market.yml"
"./internal/engine/command/market/market.yml" \
"./internal/engine/command/calculator/calculator.yml"

find . -name "*.gen.go" -exec gofumpt -l -w {} +

Expand Down
73 changes: 73 additions & 0 deletions internal/engine/command/calculator/calculator.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 10 additions & 43 deletions internal/engine/command/calculator/calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
)

type CalculatorCmd struct {
*calculatorSubCmds

clientMgr client.IManager
}

Expand All @@ -17,50 +19,15 @@ func NewCalculatorCmd(clientMgr client.IManager) *CalculatorCmd {
}

func (c *CalculatorCmd) GetCommand() *command.Command {
subCmdCalcReward := &command.Command{
Name: "reward",
Help: "Calculate the PAC coins you can earn based on your validator stake",
Args: []*command.Args{
{
Name: "stake",
Desc: "The amount of stake in your validator",
InputBox: command.InputBoxInteger,
Optional: false,
},
{
Name: "days",
Desc: "The number of days to calculate rewards for (range: 1-365)",
InputBox: command.InputBoxInteger,
Optional: false,
},
},
SubCommands: nil,
AppIDs: entity.AllAppIDs(),
Handler: c.calcRewardHandler,
TargetFlag: command.TargetMaskMainnet,
}
cmd := c.buildCalculatorCommand()
cmd.AppIDs = entity.AllAppIDs()
cmd.TargetFlag = command.TargetMaskMainnet

subCmdCalcFee := &command.Command{
Name: "fee",
Help: "Return the estimated transaction fee on the network",
SubCommands: nil,
AppIDs: entity.AllAppIDs(),
Handler: c.calcFeeHandler,
TargetFlag: command.TargetMaskMainnet,
}

cmdBlockchain := &command.Command{
Name: "calculate",
Help: "Perform calculations such as reward and fee estimations",
Args: nil,
AppIDs: entity.AllAppIDs(),
SubCommands: make([]*command.Command, 0),
Handler: nil,
TargetFlag: command.TargetMaskMainnet,
}
c.subCmdReward.AppIDs = entity.AllAppIDs()
c.subCmdReward.TargetFlag = command.TargetMaskMainnet

cmdBlockchain.AddSubCommand(subCmdCalcReward)
cmdBlockchain.AddSubCommand(subCmdCalcFee)
c.subCmdFee.AppIDs = entity.AllAppIDs()
c.subCmdFee.TargetFlag = command.TargetMaskMainnet

return cmdBlockchain
return cmd
}
29 changes: 29 additions & 0 deletions internal/engine/command/calculator/calculator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
name: calculator
emoji: 🧮
help: Perform calculations such as reward and fee estimations
sub_commands:
- name: reward
help: Calculate the PAC coins you can earn based on your validator stake
result_template: |
Approximately you earn {{.reward}} PAC reward, with {{.stake}} stake 🔒 on your validator in {{.days}} days ⏰ with {{.totalPower}} total power ⚡ of committee.
> Note📝: This number is just an estimation. It will vary depending on your stake amount and total network power.
args:
- name: stake
desc: The amount of stake in your validator
input_box: Integer
optional: false
- name: days
desc: "The number of days to calculate rewards for (range : 1-365)"
input_box: Integer
optional: false
- name: fee
help: Return the estimated transaction fee on the network
result_template: |
Sending {{.amount}} will cost {{.fee}} with current fee percentage.
args:
- name: amount
desc: The amount of PAC coins to calculate fee for
input_box: Integer
optional: false
34 changes: 34 additions & 0 deletions internal/engine/command/calculator/calculator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package calculator

import (
"testing"

"github.com/pagu-project/pagu/internal/testsuite"
"github.com/pagu-project/pagu/pkg/client"
"go.uber.org/mock/gomock"
)

type testData struct {
*testsuite.TestSuite

calculatorCmd *CalculatorCmd
mockClientMgr *client.MockIManager
}

func setup(t *testing.T) *testData {
t.Helper()

ts := testsuite.NewTestSuite(t)
ctrl := gomock.NewController(t)

mockClientMgr := client.NewMockIManager(ctrl)

calculatorCmd := NewCalculatorCmd(mockClientMgr)
calculatorCmd.buildCalculatorCommand()

return &testData{
TestSuite: ts,
calculatorCmd: calculatorCmd,
mockClientMgr: mockClientMgr,
}
}
11 changes: 4 additions & 7 deletions internal/engine/command/calculator/fee.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
package calculator

import (
"errors"

"github.com/pagu-project/pagu/internal/engine/command"
"github.com/pagu-project/pagu/internal/entity"
"github.com/pagu-project/pagu/pkg/amount"
)

func (c *CalculatorCmd) calcFeeHandler(
func (c *CalculatorCmd) feeHandler(
_ *entity.User,
cmd *command.Command,
args map[string]string,
) command.CommandResult {
amt, err := amount.FromString(args["amount"])
if err != nil {
return cmd.ErrorResult(errors.New("invalid amount param"))
return cmd.RenderFailedTemplate("Invalid amount param")
}

fee, err := c.clientMgr.GetFee(amt.ToNanoPAC())
if err != nil {
return cmd.ErrorResult(err)
return cmd.RenderErrorTemplate(err)
}

feeAmount := amount.Amount(fee)

return cmd.SuccessfulResultF("Sending %s will cost %s with current fee percentage."+
"\n> Note: Consider unbond and sortition transaction fee is 0 PAC always.", amt.String(), feeAmount.String())
return cmd.RenderResultTemplate("amount", amt, "fee", feeAmount)
}
56 changes: 56 additions & 0 deletions internal/engine/command/calculator/fee_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package calculator

import (
"errors"
"fmt"
"testing"

"github.com/pagu-project/pagu/pkg/amount"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)

func TestFeeHandler(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

td := setup(t)
cmd := td.calculatorCmd.subCmdFee

t.Run("Invalid Amount Param", func(t *testing.T) {
args := map[string]string{
"amount": "invalid",
}

result := td.calculatorCmd.feeHandler(nil, cmd, args)
assert.False(t, result.Successful)
assert.Contains(t, result.Message, "Invalid amount param")
})

t.Run("Error from GetFee", func(t *testing.T) {
args := map[string]string{
"amount": "10",
}

amt, _ := amount.FromString("10")
td.mockClientMgr.EXPECT().GetFee(amt.ToNanoPAC()).Return(int64(0), errors.New("some error"))

result := td.calculatorCmd.feeHandler(nil, cmd, args)
assert.False(t, result.Successful)
assert.Contains(t, result.Message, "some error")
})

t.Run("Successful Fee Calculation", func(t *testing.T) {
args := map[string]string{
"amount": "10",
}

amt, _ := amount.FromString("10")
td.mockClientMgr.EXPECT().GetFee(amt.ToNanoPAC()).Return(int64(100), nil)

result := td.calculatorCmd.feeHandler(nil, cmd, args)
assert.True(t, result.Successful)
assert.Contains(t, result.Message,
fmt.Sprintf("Sending %v will cost %v with current fee percentage", amt, amount.Amount(int64(100))))
})
}
25 changes: 12 additions & 13 deletions internal/engine/command/calculator/reward.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,43 @@ import (
"github.com/pagu-project/pagu/pkg/utils"
)

func (c *CalculatorCmd) calcRewardHandler(
func (c *CalculatorCmd) rewardHandler(
_ *entity.User,
cmd *command.Command,
args map[string]string,
) command.CommandResult {
stake, err := amount.FromString(args["stake"])
if err != nil {
return cmd.ErrorResult(errors.New("invalid stake param"))
return cmd.RenderFailedTemplate("Invalid stake param")
}

minStake, _ := amount.NewAmount(1)
maxStake, _ := amount.NewAmount(1000)
if stake < minStake || stake > maxStake {
return cmd.ErrorResult(
fmt.Errorf("%v is invalid amount; minimum stake amount is 1 PAC and maximum is 1,000 PAC", stake))
return cmd.RenderErrorTemplate(
fmt.Errorf("%v is invalid amount, minimum stake amount is 1 PAC and maximum is 1,000 PAC", stake))
}

numOfDays, err := strconv.Atoi(args["days"])
if err != nil {
return cmd.ErrorResult(errors.New("invalid days param"))
return cmd.RenderErrorTemplate(errors.New("invalid days param"))
}

if numOfDays < 1 || numOfDays > 365 {
return cmd.ErrorResult(fmt.Errorf("%v is invalid time; minimum time value is 1 and maximum is 365", numOfDays))
return cmd.RenderErrorTemplate(
fmt.Errorf("%v is invalid time, minimum time value is 1 and maximum is 365", numOfDays))
}

blocks := numOfDays * 8640
info, err := c.clientMgr.GetBlockchainInfo()
if err != nil {
return cmd.ErrorResult(err)
return cmd.RenderErrorTemplate(err)
}

reward := (stake.ToNanoPAC() * int64(blocks)) / info.TotalPower

return cmd.SuccessfulResultF("Approximately you earn %v PAC reward, with %v stake 🔒 on your validator "+
"in %d days ⏰ with %s total power ⚡ of committee."+
"\n\n> Note📝: This number is just an estimation. "+
"It will vary depending on your stake amount and total network power.",
utils.FormatNumber(reward), stake, numOfDays,
utils.FormatNumber(int64(amount.Amount(info.TotalPower).ToPAC())))
return cmd.RenderResultTemplate("stake", stake,
"days", numOfDays,
"totalPower", utils.FormatNumber(int64(amount.Amount(info.TotalPower).ToPAC())),
"reward", utils.FormatNumber(reward))
}
Loading

0 comments on commit e80bc22

Please sign in to comment.