Skip to content

Commit

Permalink
feat: add support for custom drip amounts (#23)
Browse files Browse the repository at this point in the history
* Add support for custom drip amounts

* Minor tidy

* Fix typo

* Revert go mod changes from previous versions
  • Loading branch information
zivkovicmilos authored Mar 13, 2024
1 parent db68cdf commit f72cfa2
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 40 deletions.
6 changes: 3 additions & 3 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ func (c *faucetCfg) registerRootFlags(fs *flag.FlagSet) {
)

fs.StringVar(
&c.config.SendAmount,
&c.config.MaxSendAmount,
"send-amount",
config.DefaultSendAmount,
"the static send amount (native currency)",
config.DefaultMaxSendAmount,
"the static max send amount per drip (native currency)",
)

fs.StringVar(
Expand Down
10 changes: 5 additions & 5 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
const (
DefaultListenAddress = "0.0.0.0:8545"
DefaultChainID = "dev"
DefaultSendAmount = "1000000ugnot"
DefaultMaxSendAmount = "1000000ugnot"
//nolint:lll // Mnemonic is naturally long
DefaultMnemonic = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast"
DefaultNumAccounts = uint64(1)
Expand Down Expand Up @@ -45,9 +45,9 @@ type Config struct {
// The mnemonic for the faucet
Mnemonic string `toml:"mnemonic"`

// The static send amount (native currency).
// The static max send amount (native currency).
// Format should be: <AMOUNT>ugnot
SendAmount string `toml:"send_amount"`
MaxSendAmount string `toml:"send_amount"`

// The number of faucet accounts,
// based on the mnemonic (account 0, index x)
Expand All @@ -59,7 +59,7 @@ func DefaultConfig() *Config {
return &Config{
ListenAddress: DefaultListenAddress,
ChainID: DefaultChainID,
SendAmount: DefaultSendAmount,
MaxSendAmount: DefaultMaxSendAmount,
Mnemonic: DefaultMnemonic,
NumAccounts: DefaultNumAccounts,
CORSConfig: DefaultCORSConfig(),
Expand All @@ -79,7 +79,7 @@ func ValidateConfig(config *Config) error {
}

// validate the send amount
if !amountRegex.MatchString(config.SendAmount) {
if !amountRegex.MatchString(config.MaxSendAmount) {
return ErrInvalidSendAmount
}

Expand Down
2 changes: 1 addition & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestConfig_ValidateConfig(t *testing.T) {
t.Parallel()

cfg := DefaultConfig()
cfg.SendAmount = "1000goo" // invalid denom
cfg.MaxSendAmount = "1000goo" // invalid denom

assert.ErrorIs(t, ValidateConfig(cfg), ErrInvalidSendAmount)
})
Expand Down
6 changes: 3 additions & 3 deletions faucet.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type Faucet struct {
handlers []Handler // request handlers
prepareTxMsgFn PrepareTxMessageFn // transaction message creator

sendAmount std.Coins // for fast lookup
maxSendAmount std.Coins // the max send amount per drip
}

var noopLogger = slog.New(slog.NewTextHandler(io.Discard, nil))
Expand Down Expand Up @@ -76,8 +76,8 @@ func NewFaucet(
}

// Set the send amount
//nolint:errcheck // SendAmount is validated beforehand
f.sendAmount, _ = std.ParseCoins(f.config.SendAmount)
//nolint:errcheck // MaxSendAmount is validated beforehand
f.maxSendAmount, _ = std.ParseCoins(f.config.MaxSendAmount)

// Generate the in-memory keyring
f.keyring = memory.New(f.config.Mnemonic, f.config.NumAccounts)
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ module github.com/gnolang/faucet
go 1.21

require (
github.com/gnolang/gno v0.0.0-20240308113041-45c8f900a1a3
github.com/gnolang/gno v0.0.0-20240313211052-3481a03c98bc
github.com/go-chi/chi/v5 v5.0.12
github.com/pelletier/go-toml v1.9.5
github.com/peterbourgon/ff/v3 v3.4.0
github.com/rs/cors v1.10.1
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.8.4
golang.org/x/sync v0.6.0
)

Expand All @@ -22,8 +22,8 @@ require (
github.com/gorilla/websocket v1.5.1 // indirect
github.com/jaekwon/testify v1.6.1 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/linxGnu/grocksdb v1.8.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
Expand Down
16 changes: 9 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
Expand All @@ -43,8 +44,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gnolang/gno v0.0.0-20240308113041-45c8f900a1a3 h1:jD9i4n582op16xHU7aKPtp3Oo7rJ8Q5+jpR6nE/ad6U=
github.com/gnolang/gno v0.0.0-20240308113041-45c8f900a1a3/go.mod h1:jDARzJA+/H5YwCGpWuouqo4D0LMSNZVVgFQK/r/R7As=
github.com/gnolang/gno v0.0.0-20240313211052-3481a03c98bc h1:aYkkNfumtt9z8DeI7ZiFC+vMgFFadaGY0A97pXpOqZU=
github.com/gnolang/gno v0.0.0-20240313211052-3481a03c98bc/go.mod h1:jDARzJA+/H5YwCGpWuouqo4D0LMSNZVVgFQK/r/R7As=
github.com/gnolang/goleveldb v0.0.9 h1:Q7rGko9oXMKtQA+Apeeed5a3sjba/mcDhzJGoTVLCKE=
github.com/gnolang/goleveldb v0.0.9/go.mod h1:Dz6p9bmpy/FBESTgduiThZt5mToVDipcHGzj/zUOo8E=
github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
Expand Down Expand Up @@ -83,14 +84,15 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/linxGnu/grocksdb v1.8.4 h1:ZMsBpPpJNtRLHiKKp0mI7gW+NT4s7UgfD5xHxx1jVRo=
github.com/linxGnu/grocksdb v1.8.4/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY=
github.com/linxGnu/grocksdb v1.6.20 h1:C0SNv12/OBr/zOdGw6reXS+mKpIdQGb/AkZWjHYnO64=
github.com/linxGnu/grocksdb v1.6.20/go.mod h1:IbTMGpmWg/1pg2hcG9LlxkqyqiJymdCweaUrzsLRFmg=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand All @@ -111,8 +113,8 @@ github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
Expand Down
62 changes: 60 additions & 2 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,25 @@ import (
"fmt"
"io"
"net/http"
"regexp"

"github.com/gnolang/faucet/writer"
httpWriter "github.com/gnolang/faucet/writer/http"
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/std"
)

const (
unableToHandleRequest = "unable to handle faucet request"
faucetSuccess = "successfully executed faucet transfer"
)

var errInvalidBeneficiary = errors.New("invalid beneficiary address")
var (
errInvalidBeneficiary = errors.New("invalid beneficiary address")
errInvalidSendAmount = errors.New("invalid send amount")
)

var amountRegex = regexp.MustCompile(`^\d+ugnot$`)

// defaultHTTPHandler is the default faucet transfer handler
func (f *Faucet) defaultHTTPHandler(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -74,8 +81,39 @@ func (f *Faucet) handleRequest(writer writer.ResponseWriter, requests Requests)
continue
}

// Extract the send amount
amount, err := extractSendAmount(baseRequest)
if err != nil {
// Save the error response
responses[i] = Response{
Result: unableToHandleRequest,
Error: err.Error(),
}

continue
}

// Check if the amount is set
if amount.IsZero() {
// Drip amount is not set, use
// the max faucet drip amount
amount = f.maxSendAmount
}

// Check if the amount exceeds the max
// drip amount for the faucet
if amount.IsAllGT(f.maxSendAmount) {
// Save the error response
responses[i] = Response{
Result: unableToHandleRequest,
Error: errInvalidSendAmount.Error(),
}

continue
}

// Run the method handler
if err := f.transferFunds(beneficiary); err != nil {
if err := f.transferFunds(beneficiary, amount); err != nil {
f.logger.Debug(
unableToHandleRequest,
"request",
Expand Down Expand Up @@ -144,3 +182,23 @@ func extractBeneficiary(request Request) (crypto.Address, error) {

return beneficiary, nil
}

// extractSendAmount extracts the drip amount from the base faucet request, if any
func extractSendAmount(request Request) (std.Coins, error) {
// Check if the amount is set
if request.Amount == "" {
return std.Coins{}, nil
}

// Validate the send amount is valid
if !amountRegex.MatchString(request.Amount) {
return std.Coins{}, errInvalidSendAmount
}

amount, err := std.ParseCoins(request.Amount)
if err != nil {
return std.Coins{}, fmt.Errorf("%w, %w", errInvalidSendAmount, err)
}

return amount, nil
}
103 changes: 100 additions & 3 deletions handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestFaucet_Serve_ValidRequests(t *testing.T) {

var (
gasFee = std.MustParseCoin("1ugnot")
sendAmount = std.MustParseCoins(config.DefaultSendAmount)
sendAmount = std.MustParseCoins(config.DefaultMaxSendAmount)
)

var (
Expand Down Expand Up @@ -295,7 +295,7 @@ func TestFaucet_Serve_InvalidRequests(t *testing.T) {

var (
gasFee = std.MustParseCoin("1ugnot")
sendAmount = std.MustParseCoins(config.DefaultSendAmount)
sendAmount = std.MustParseCoins(config.DefaultMaxSendAmount)
)

var (
Expand Down Expand Up @@ -569,7 +569,7 @@ func TestFaucet_Serve_NoFundedAccounts(t *testing.T) {

var (
gasFee = std.MustParseCoin("1ugnot")
sendAmount = std.MustParseCoins(config.DefaultSendAmount)
sendAmount = std.MustParseCoins(config.DefaultMaxSendAmount)
)

var (
Expand Down Expand Up @@ -713,3 +713,100 @@ func TestFaucet_Serve_NoFundedAccounts(t *testing.T) {
// Validate the broadcast tx
assert.Nil(t, capturedTxs)
}

func TestFaucet_Serve_InvalidSendAmount(t *testing.T) {
t.Parallel()

// Extract the default send amount
maxSendAmount := std.MustParseCoins(config.DefaultMaxSendAmount)

testTable := []struct {
name string
sendAmount std.Coins
}{
{
"invalid send amount",
std.NewCoins(std.NewCoin("atom", 10)),
},
{
"excessive send amount",
maxSendAmount.Add(std.MustParseCoins("100ugnot")),
},
}

for _, testCase := range testTable {
testCase := testCase

t.Run(testCase.name, func(t *testing.T) {
t.Parallel()

var (
validAddress = crypto.MustAddressFromString("g155n659f89cfak0zgy575yqma64sm4tv6exqk99")
gasFee = std.MustParseCoin("1ugnot")

singleInvalidRequest = Request{
To: validAddress.String(),
Amount: testCase.sendAmount.String(),
}
)

encodedSingleInvalidRequest, err := json.Marshal(
singleInvalidRequest,
)
require.NoError(t, err)

getFaucetURL := func(address string) string {
return fmt.Sprintf("http://%s", address)
}

// Create a new faucet with default params
cfg := config.DefaultConfig()
cfg.ListenAddress = fmt.Sprintf("127.0.0.1:%d", getFreePort(t))
cfg.MaxSendAmount = maxSendAmount.String()

f, err := NewFaucet(
static.New(gasFee, 100000),
&mockClient{},
WithConfig(cfg),
)

require.NoError(t, err)
require.NotNil(t, f)

// Start the faucet
ctx, cancelFn := context.WithCancel(context.Background())
defer cancelFn()

g, gCtx := errgroup.WithContext(ctx)

g.Go(func() error {
return f.Serve(gCtx)
})

url := getFaucetURL(f.config.ListenAddress)

// Wait for the faucet to be started
waitForServer(t, url)

// Execute the request
respRaw, err := http.Post(
url,
jsonMimeType,
bytes.NewBuffer(encodedSingleInvalidRequest),
)
require.NoError(t, err)

respBytes, err := io.ReadAll(respRaw.Body)
require.NoError(t, err)

response := decodeResponse[Response](t, respBytes)

assert.Contains(t, response.Error, errInvalidSendAmount.Error())
assert.Equal(t, unableToHandleRequest, response.Result)

// Stop the faucet and wait for it to finish
cancelFn()
assert.NoError(t, g.Wait())
})
}
}
Loading

0 comments on commit f72cfa2

Please sign in to comment.