Skip to content

Commit

Permalink
Refactor withdrawals code
Browse files Browse the repository at this point in the history
  • Loading branch information
Zaptoss committed May 23, 2024
1 parent 0648d59 commit 16afe02
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 10 deletions.
5 changes: 5 additions & 0 deletions docs/spec/components/schemas/Withdraw.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ allOf:
required:
- amount
- address
- proof
properties:
amount:
type: integer
Expand All @@ -20,3 +21,7 @@ allOf:
type: string
description: Rarimo address to withdraw to. Can be any valid address.
example: rarimo15hcd6tv7pe8hk2re7hu0zg0aphqdm2dtjrs0ds
proof:
type: object
format: json.RawMessage
description: JSON encoded ZK passport verification proof.
44 changes: 37 additions & 7 deletions internal/service/handlers/withdraw.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
package handlers

import (
"encoding/json"
"fmt"
"math/big"
"net/http"

cosmos "github.com/cosmos/cosmos-sdk/types"
bank "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/ethereum/go-ethereum/common/hexutil"
validation "github.com/go-ozzo/ozzo-validation/v4"
zkptypes "github.com/iden3/go-rapidsnark/types"
"github.com/rarimo/decentralized-auth-svc/pkg/auth"
"github.com/rarimo/rarime-points-svc/internal/data"
"github.com/rarimo/rarime-points-svc/internal/service/requests"
"github.com/rarimo/rarime-points-svc/resources"
zk "github.com/rarimo/zkverifier-kit"
"gitlab.com/distributed_lab/ape"
"gitlab.com/distributed_lab/ape/problems"
"gitlab.com/distributed_lab/logan/v3/errors"
)

const usaAuthorithy = "8571562"

func Withdraw(w http.ResponseWriter, r *http.Request) {
req, err := requests.NewWithdraw(r)
if err != nil {
Expand All @@ -33,12 +41,14 @@ func Withdraw(w http.ResponseWriter, r *http.Request) {
return
}

if !auth.Authenticates(UserClaims(r), auth.UserGrant(req.Data.ID)) {
nullifier := req.Data.ID

if !auth.Authenticates(UserClaims(r), auth.UserGrant(nullifier)) {
ape.RenderErr(w, problems.Unauthorized())
return
}

balance, err := BalancesQ(r).FilterByNullifier(req.Data.ID).Get()
balance, err := BalancesQ(r).FilterByNullifier(nullifier).Get()
if err != nil {
log.WithError(err).Error("Failed to get balance by nullifier")
ape.RenderErr(w, problems.InternalError())
Expand All @@ -50,20 +60,38 @@ func Withdraw(w http.ResponseWriter, r *http.Request) {
return
}

if err = isEligibleToWithdraw(balance, req.Data.Attributes.Amount); err != nil {
var proof zkptypes.ZKProof
if err := json.Unmarshal(req.Data.Attributes.Proof, &proof); err != nil {
ape.RenderErr(w, problems.BadRequest(err)...)
return
}

// MustDecode will never panic, because of the previous logic
proof.PubSignals[zk.Nullifier] = new(big.Int).SetBytes(hexutil.MustDecode(nullifier)).String()
if err := Verifier(r).VerifyProof(proof, zk.WithProofSelectorValue("23073")); err != nil {
ape.RenderErr(w, problems.BadRequest(err)...)
return
}

if proof.PubSignals[zk.Citizenship] == usaAuthorithy {
ape.RenderErr(w, problems.BadRequest(validation.Errors{"authority": errors.New("Incorrect authority")})...)
return
}

if err = isEligibleToWithdraw(r, balance, req.Data.Attributes.Amount); err != nil {
ape.RenderErr(w, problems.BadRequest(err)...)
return
}

var withdrawal *data.Withdrawal
err = EventsQ(r).Transaction(func() error {
err = BalancesQ(r).FilterByNullifier(req.Data.ID).UpdateAmountBy(-req.Data.Attributes.Amount)
err = BalancesQ(r).FilterByNullifier(nullifier).UpdateAmountBy(-req.Data.Attributes.Amount)
if err != nil {
return fmt.Errorf("decrease points amount: %w", err)
}

withdrawal, err = WithdrawalsQ(r).Insert(data.Withdrawal{
Nullifier: req.Data.ID,
Nullifier: nullifier,
Amount: req.Data.Attributes.Amount,
Address: req.Data.Attributes.Address,
})
Expand All @@ -84,7 +112,7 @@ func Withdraw(w http.ResponseWriter, r *http.Request) {
}

// balance should exist cause of previous logic
balance, err = BalancesQ(r).GetWithRank(req.Data.ID)
balance, err = BalancesQ(r).GetWithRank(nullifier)
if err != nil {
log.WithError(err).Error("Failed to get balance by nullifier with rank")
ape.RenderErr(w, problems.InternalError())
Expand Down Expand Up @@ -112,7 +140,7 @@ func newWithdrawResponse(w data.Withdrawal, balance data.Balance) *resources.Wit
return &resp
}

func isEligibleToWithdraw(balance *data.Balance, amount int64) error {
func isEligibleToWithdraw(r *http.Request, balance *data.Balance, amount int64) error {
mapValidationErr := func(field, format string, a ...any) validation.Errors {
return validation.Errors{
field: fmt.Errorf(format, a...),
Expand All @@ -124,6 +152,8 @@ func isEligibleToWithdraw(balance *data.Balance, amount int64) error {
return mapValidationErr("is_disabled", "user must be referred to withdraw")
case balance.Amount < amount:
return mapValidationErr("data/attributes/amount", "insufficient balance: %d", balance.Amount)
case !Levels(r)[balance.Level].WithdrawalAllowed:
return mapValidationErr("withdrawal not allowed", "user must up level to have withdraw ability")
}

return nil
Expand Down
10 changes: 7 additions & 3 deletions internal/service/requests/withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,26 @@ package requests
import (
"encoding/json"
"net/http"
"strings"

"github.com/go-chi/chi"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/rarimo/rarime-points-svc/resources"
)

func NewWithdraw(r *http.Request) (req resources.WithdrawRequest, err error) {
nullifier := chi.URLParam(r, "nullifier")

if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
err = newDecodeError("body", err)
return
}

req.Data.ID = strings.ToLower(req.Data.ID)

return req, validation.Errors{
"data/id": validation.Validate(req.Data.ID, validation.Required, validation.In(nullifier)),
"data/id": validation.Validate(req.Data.ID,
validation.Required,
validation.In(strings.ToLower(chi.URLParam(r, "nullifier"))),
validation.Match(nullifierRegexp)),
"data/type": validation.Validate(req.Data.Type, validation.Required, validation.In(resources.WITHDRAW)),
"data/attributes/amount": validation.Validate(req.Data.Attributes.Amount, validation.Required, validation.Min(1)),
"data/attributes/address": validation.Validate(req.Data.Attributes.Address, validation.Required),
Expand Down
4 changes: 4 additions & 0 deletions resources/model_withdraw_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

package resources

import "encoding/json"

type WithdrawAttributes struct {
// Rarimo address to withdraw to. Can be any valid address.
Address string `json:"address"`
// Amount of points to withdraw
Amount int64 `json:"amount"`
// JSON encoded ZK passport verification proof.
Proof json.RawMessage `json:"proof"`
}

0 comments on commit 16afe02

Please sign in to comment.