diff --git a/.github/workflows/manual-deploy-testnet-faucet.yml b/.github/workflows/manual-deploy-testnet-faucet.yml index 1c19efede2..4e8245a0ff 100644 --- a/.github/workflows/manual-deploy-testnet-faucet.yml +++ b/.github/workflows/manual-deploy-testnet-faucet.yml @@ -21,6 +21,7 @@ on: options: - 'dev-testnet' - 'testnet' + - 'sepolia-testnet' workflow_call: inputs: @@ -45,12 +46,21 @@ jobs: run: | echo "FAUCET_BUILD_TAG=testnetobscuronet.azurecr.io/obscuronet/faucet_testnet:latest" >> $GITHUB_ENV echo "TESTNET_ADDR=erpc.testnet.obscu.ro" >> $GITHUB_ENV + echo "DEFAULT_FAUCET_AMOUNT=100" >> $GITHUB_ENV - name: 'Sets env vars for dev-testnet' if: ${{ inputs.testnet_type == 'dev-testnet' }} run: | echo "FAUCET_BUILD_TAG=testnetobscuronet.azurecr.io/obscuronet/dev_faucet_testnet:latest" >> $GITHUB_ENV echo "TESTNET_ADDR=erpc.dev-testnet.obscu.ro" >> $GITHUB_ENV + echo "DEFAULT_FAUCET_AMOUNT=100" >> $GITHUB_ENV + + - name: 'Sets env vars for sepolia-testnet' + if: ${{ inputs.testnet_type == 'sepolia-testnet' }} + run: | + echo "FAUCET_BUILD_TAG=testnetobscuronet.azurecr.io/obscuronet/sepolia_faucet_testnet:latest" >> $GITHUB_ENV + echo "TESTNET_ADDR=erpc.sepolia-testnet.obscu.ro" >> $GITHUB_ENV + echo "DEFAULT_FAUCET_AMOUNT=0.5" >> $GITHUB_ENV - name: 'Login to Azure docker registry' uses: azure/docker-login@v1 @@ -79,7 +89,7 @@ jobs: location: 'uksouth' restart-policy: 'Never' environment-variables: PORT=80 - command-line: ./faucet --nodeHost ${{ env.TESTNET_ADDR }} --pk ${{ secrets.FAUCET_PK }} --jwtSecret ${{ secrets.FAUCET_JWT_SECRET }} + command-line: ./faucet --nodeHost ${{ env.TESTNET_ADDR }} --pk ${{ secrets.FAUCET_PK }} --jwtSecret ${{ secrets.FAUCET_JWT_SECRET }} --defaultAmount ${{ env.DEFAULT_FAUCET_AMOUNT }} ports: '80' cpu: 2 memory: 2 diff --git a/README.md b/README.md index 181330f568..c1d5c5af9f 100644 --- a/README.md +++ b/README.md @@ -363,7 +363,7 @@ it for an allocation to an externally owned addressed e.g. for the account `0x0d the following curl command can be used; ```bash -curl --location --request POST 'http://127.0.0.1:8080/fund/obx' \ +curl --location --request POST 'http://127.0.0.1:8080/fund/eth' \ --header 'Content-Type: application/json' \ --data-raw '{ "address":"0x0d2166b7b3A1522186E809e83d925d7b0B6db084" }' ``` diff --git a/docs/_docs/testnet/deploying-a-smart-contract-programmatically.py b/docs/_docs/testnet/deploying-a-smart-contract-programmatically.py index e2e9004250..5b56bfb62b 100644 --- a/docs/_docs/testnet/deploying-a-smart-contract-programmatically.py +++ b/docs/_docs/testnet/deploying-a-smart-contract-programmatically.py @@ -12,7 +12,7 @@ WHOST = '127.0.0.1' LOWER = 0 UPPER = 100 -FAUCET_URL = 'http://testnet-faucet.uksouth.azurecontainer.io/fund/obx' +FAUCET_URL = 'http://testnet-faucet.uksouth.azurecontainer.io/fund/eth' guesser = ''' // SPDX-License-Identifier: MIT diff --git a/docs/_docs/testnet/faucet.md b/docs/_docs/testnet/faucet.md index 567301bd31..2eb484a43b 100644 --- a/docs/_docs/testnet/faucet.md +++ b/docs/_docs/testnet/faucet.md @@ -30,6 +30,6 @@ the faucet server using the below; 1. Make a note of your wallet address or copy it to your clipboard. 2. Open a command shell and issue the below command, where `
` should be replaced with the value stored in your clipboard (e.g. `0x75Ad715443e1E2EBdaFA33ABB3B08443966019A6`). The faucet server will credit 100,000 OBX by default. ```bash -curl --location --request POST 'http://testnet-faucet.uksouth.azurecontainer.io/fund/obx' --header 'Content-Type: application/json' --data-raw '{ "address":"" }' +curl --location --request POST 'http://testnet-faucet.uksouth.azurecontainer.io/fund/eth' --header 'Content-Type: application/json' --data-raw '{ "address":"" }' ``` 3. After a short period of time the curl command will return `{"status":"ok"}` confirming OBX have been credited to your wallet. diff --git a/integration/faucet/faucet_test.go b/integration/faucet/faucet_test.go index 5d072634d8..0ddbae6964 100644 --- a/integration/faucet/faucet_test.go +++ b/integration/faucet/faucet_test.go @@ -45,13 +45,14 @@ func TestFaucet(t *testing.T) { time.Sleep(2 * time.Second) faucetConfig := &faucet.Config{ - Port: startPort, - Host: "localhost", - HTTPPort: startPort + integration.DefaultHostRPCHTTPOffset, - PK: "0x" + contractDeployerPrivateKeyHex, - JWTSecret: "This_is_secret", - ChainID: big.NewInt(integration.ObscuroChainID), - ServerPort: integration.StartPortFaucetHTTPUnitTest, + Port: startPort, + Host: "localhost", + HTTPPort: startPort + integration.DefaultHostRPCHTTPOffset, + PK: "0x" + contractDeployerPrivateKeyHex, + JWTSecret: "This_is_secret", + ChainID: big.NewInt(integration.ObscuroChainID), + ServerPort: integration.StartPortFaucetHTTPUnitTest, + DefaultFundAmount: new(big.Int).Mul(big.NewInt(100), big.NewInt(1e18)), } faucetContainer, err := container.NewFaucetContainerFromConfig(faucetConfig) assert.NoError(t, err) @@ -98,7 +99,7 @@ func createObscuroNetwork(t *testing.T, startPort int) { } func fundWallet(port int, w wallet.Wallet) error { - url := fmt.Sprintf("http://localhost:%d/fund/obx", port) + url := fmt.Sprintf("http://localhost:%d/fund/eth", port) method := "POST" payload := strings.NewReader(fmt.Sprintf(`{"address":"%s"}`, w.Address())) diff --git a/integration/networktest/env/network_setup.go b/integration/networktest/env/network_setup.go index 8367a9db73..fecffd1d75 100644 --- a/integration/networktest/env/network_setup.go +++ b/integration/networktest/env/network_setup.go @@ -9,7 +9,7 @@ func Testnet() networktest.Environment { connector := NewTestnetConnector( "http://erpc.testnet.obscu.ro:80", []string{"http://erpc.testnet.obscu.ro:80"}, // for now we'll just use sequencer as validator node... todo (@matt) - "http://testnet-faucet.uksouth.azurecontainer.io/fund/obx", + "http://testnet-faucet.uksouth.azurecontainer.io/fund/eth", "ws://testnet-eth2network.uksouth.cloudapp.azure.com:9000", ) return &testnetEnv{connector} @@ -19,7 +19,7 @@ func DevTestnet() networktest.Environment { connector := NewTestnetConnector( "http://erpc.dev-testnet.obscu.ro:80", []string{"http://erpc.dev-testnet.obscu.ro:80"}, // for now we'll just use sequencer as validator node... todo (@matt) - "http://dev-testnet-faucet.uksouth.azurecontainer.io/fund/obx", + "http://dev-testnet-faucet.uksouth.azurecontainer.io/fund/eth", "ws://dev-testnet-eth2network.uksouth.cloudapp.azure.com:9000", ) return &testnetEnv{connector} diff --git a/tools/faucet/README.md b/tools/faucet/README.md index 19645acef0..aff38ea1e6 100644 --- a/tools/faucet/README.md +++ b/tools/faucet/README.md @@ -31,11 +31,11 @@ on port `80` within the container, but maps port `8080` on the host machine to t ## Allocating OBX to an EOA on a local testnet -Allocating OBX to an externally owned account is done through a POST command to the `/fund/obx` endpoint, where the +Allocating OBX to an externally owned account is done through a POST command to the `/fund/eth` endpoint, where the data in the POST command specifies the address e.g. for the account `0x0d2166b7b3A1522186E809e83d925d7b0B6db084` ```bash -curl --location --request POST 'http://127.0.0.1:8080/fund/obx' \ +curl --location --request POST 'http://127.0.0.1:8080/fund/eth' \ --header 'Content-Type: application/json' \ --data-raw '{ "address":"0x0d2166b7b3A1522186E809e83d925d7b0B6db084" }' ``` diff --git a/tools/faucet/cmd/cli.go b/tools/faucet/cmd/cli.go index 78dbdb8646..b395823904 100644 --- a/tools/faucet/cmd/cli.go +++ b/tools/faucet/cmd/cli.go @@ -4,6 +4,8 @@ import ( "flag" "math/big" + "github.com/ethereum/go-ethereum/params" + "github.com/obscuronet/go-obscuro/tools/faucet/faucet" ) @@ -32,6 +34,10 @@ const ( serverPortName = "serverPort" serverPortDefault = 80 serverPortUsage = "Port where the web server binds to" + + defaultAmountName = "defaultAmount" + defaultAmountDefault = 100.0 + defaultAmountUsage = "Default amount of token to fund (in ETH)" ) func parseCLIArgs() *faucet.Config { @@ -41,15 +47,25 @@ func parseCLIArgs() *faucet.Config { faucetPK := flag.String(faucetPKName, faucetPKDefault, faucetPKUsage) jwtSecret := flag.String(jwtSecretName, jwtSecretDefault, jwtSecretUsage) serverPort := flag.Int(serverPortName, serverPortDefault, serverPortUsage) + defaultAmount := flag.Float64(defaultAmountName, defaultAmountDefault, defaultAmountUsage) flag.Parse() return &faucet.Config{ - Port: *faucetPort, - Host: *nodeHost, - HTTPPort: *nodeHTTPPort, - PK: *faucetPK, - JWTSecret: *jwtSecret, - ServerPort: *serverPort, - ChainID: big.NewInt(443), // TODO make this configurable + Port: *faucetPort, + Host: *nodeHost, + HTTPPort: *nodeHTTPPort, + PK: *faucetPK, + JWTSecret: *jwtSecret, + ServerPort: *serverPort, + ChainID: big.NewInt(443), // TODO make this configurable + DefaultFundAmount: toWei(defaultAmount), } } + +func toWei(amount *float64) *big.Int { + amtFloat := new(big.Float).SetFloat64(*amount) + weiFloat := new(big.Float).Mul(amtFloat, big.NewFloat(params.Ether)) + // don't care about the accuracy here, float should have less than 18 decimal places + wei, _ := weiFloat.Int(nil) + return wei +} diff --git a/tools/faucet/container/faucet_container.go b/tools/faucet/container/faucet_container.go index befefdc239..78718fcf16 100644 --- a/tools/faucet/container/faucet_container.go +++ b/tools/faucet/container/faucet_container.go @@ -21,7 +21,7 @@ func NewFaucetContainerFromConfig(cfg *faucet.Config) (*FaucetContainer, error) return nil, err } bindAddress := fmt.Sprintf(":%d", cfg.ServerPort) - server := webserver.NewWebServer(f, bindAddress, []byte(cfg.JWTSecret)) + server := webserver.NewWebServer(f, bindAddress, []byte(cfg.JWTSecret), cfg.DefaultFundAmount) return NewFaucetContainer(f, server) } diff --git a/tools/faucet/faucet/faucet.go b/tools/faucet/faucet/faucet.go index f83d68f435..ff5d5e6a37 100644 --- a/tools/faucet/faucet/faucet.go +++ b/tools/faucet/faucet/faucet.go @@ -13,18 +13,19 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" "github.com/obscuronet/go-obscuro/go/obsclient" "github.com/obscuronet/go-obscuro/go/rpc" "github.com/obscuronet/go-obscuro/go/wallet" ) const ( - _timeout = 60 * time.Second - OBXNativeToken = "obx" - WrappedOBX = "wobx" - WrappedEth = "weth" - WrappedUSDC = "usdc" + _timeout = 60 * time.Second + NativeToken = "eth" + // DeprecatedNativeToken is left in temporarily for tooling that is getting native funds using `/obx` URL + DeprecatedNativeToken = "obx" // todo (@matt) remove this once we have fixed the /obx usages + WrappedOBX = "wobx" + WrappedEth = "weth" + WrappedUSDC = "usdc" ) type Faucet struct { @@ -50,11 +51,11 @@ func NewFaucet(rpcURL string, chainID int64, pkString string) (*Faucet, error) { }, nil } -func (f *Faucet) Fund(address *common.Address, token string, amount int64) error { +func (f *Faucet) Fund(address *common.Address, token string, amount *big.Int) error { var err error var signedTx *types.Transaction - if token == OBXNativeToken { + if token == NativeToken || token == DeprecatedNativeToken { signedTx, err = f.fundNativeToken(address, amount) } else { return fmt.Errorf("token not fundable atm") @@ -105,7 +106,7 @@ func (f *Faucet) validateTx(tx *types.Transaction) error { return fmt.Errorf("unable to fetch tx receipt after %s", _timeout) } -func (f *Faucet) fundNativeToken(address *common.Address, amount int64) (*types.Transaction, error) { +func (f *Faucet) fundNativeToken(address *common.Address, amount *big.Int) (*types.Transaction, error) { // only one funding at the time f.fundMutex.Lock() defer f.fundMutex.Unlock() @@ -125,7 +126,7 @@ func (f *Faucet) fundNativeToken(address *common.Address, amount int64) (*types. GasPrice: big.NewInt(225), Gas: gas, To: address, - Value: new(big.Int).Mul(big.NewInt(amount), big.NewInt(params.Ether)), + Value: amount, } signedTx, err := f.wallet.SignTransaction(tx) diff --git a/tools/faucet/faucet/faucet_config.go b/tools/faucet/faucet/faucet_config.go index 1b82dbdf92..81ccb1db41 100644 --- a/tools/faucet/faucet/faucet_config.go +++ b/tools/faucet/faucet/faucet_config.go @@ -3,11 +3,12 @@ package faucet import "math/big" type Config struct { - Port int - Host string - HTTPPort int - PK string - JWTSecret string - ChainID *big.Int - ServerPort int + Port int + Host string + HTTPPort int + PK string + JWTSecret string + ChainID *big.Int + ServerPort int + DefaultFundAmount *big.Int // how much token to fund by default (in wei) } diff --git a/tools/faucet/webserver/web_server.go b/tools/faucet/webserver/web_server.go index 1d76524bd0..f3bf7967bb 100644 --- a/tools/faucet/webserver/web_server.go +++ b/tools/faucet/webserver/web_server.go @@ -3,6 +3,7 @@ package webserver import ( "context" "fmt" + "math/big" "net/http" "strings" "time" @@ -24,80 +25,39 @@ type requestAddr struct { Address string `json:"address" binding:"required"` } -func NewWebServer(faucetServer *faucet.Faucet, bindAddress string, jwtSecret []byte) *WebServer { +func NewWebServer(faucetServer *faucet.Faucet, bindAddress string, jwtSecret []byte, defaultAmount *big.Int) *WebServer { r := gin.New() gin.SetMode(gin.ReleaseMode) - // todo move this declaration out of this scope - parseFunding := func(c *gin.Context) { - tokenReq := c.Params.ByName("token") - var token string - - // check the token request type - switch tokenReq { - case faucet.OBXNativeToken: - token = faucet.OBXNativeToken - case faucet.WrappedOBX: - token = faucet.WrappedOBX - case faucet.WrappedEth: - token = faucet.WrappedEth - case faucet.WrappedUSDC: - token = faucet.WrappedUSDC - default: - errorHandler(c, fmt.Errorf("token not recognized: %s", tokenReq), faucetServer.Logger) - return - } - - // make sure there's an address - var req requestAddr - if err := c.Bind(&req); err != nil { - errorHandler(c, fmt.Errorf("unable to parse request: %w", err), faucetServer.Logger) - return - } - - // make sure the address is valid - if !common.IsHexAddress(req.Address) { - errorHandler(c, fmt.Errorf("unexpected address %s", req.Address), faucetServer.Logger) - return - } - - amount := int64(100) + // authed endpoint + r.POST("/auth/fund/:token", jwtTokenChecker(jwtSecret, faucetServer.Logger), fundingHandler(faucetServer, defaultAmount)) - // fund the address - addr := common.HexToAddress(req.Address) - if err := faucetServer.Fund(&addr, token, amount); err != nil { - errorHandler(c, fmt.Errorf("unable to fund request %w", err), faucetServer.Logger) - return - } + // todo (@matt) we need to remove this unsecure endpoint before we provide a fully public sepolia faucet + r.POST("/fund/:token", fundingHandler(faucetServer, defaultAmount)) - c.JSON(http.StatusOK, gin.H{"status": "ok"}) + return &WebServer{ + engine: r, + faucet: faucetServer, + bindAddress: bindAddress, } +} - jwtTokenCheck := func(c *gin.Context) { +func jwtTokenChecker(jwtSecret []byte, logger log.Logger) gin.HandlerFunc { + return func(c *gin.Context) { jwtToken, err := extractBearerToken(c.GetHeader("Authorization")) if err != nil { - errorHandler(c, err, faucetServer.Logger) + errorHandler(c, err, logger) return } _, err = faucet.ValidateToken(jwtToken, jwtSecret) if err != nil { - errorHandler(c, err, faucetServer.Logger) + errorHandler(c, err, logger) return } c.Next() } - // authed endpoint - r.POST("/auth/fund/:token", jwtTokenCheck, parseFunding) - - r.POST("/fund/:token", parseFunding) - - return &WebServer{ - engine: r, - faucet: faucetServer, - bindAddress: bindAddress, - } } func (w *WebServer) Start() error { @@ -146,3 +106,50 @@ func extractBearerToken(header string) (string, error) { return jwtToken[1], nil } + +func fundingHandler(faucetServer *faucet.Faucet, defaultAmount *big.Int) gin.HandlerFunc { + return func(c *gin.Context) { + tokenReq := c.Params.ByName("token") + var token string + + // check the token request type + switch tokenReq { + case faucet.NativeToken: + token = faucet.NativeToken + // we leave this option in temporarily for tools that are still using `/obx` endpoint for native funds + case faucet.DeprecatedNativeToken: + token = faucet.NativeToken + case faucet.WrappedOBX: + token = faucet.WrappedOBX + case faucet.WrappedEth: + token = faucet.WrappedEth + case faucet.WrappedUSDC: + token = faucet.WrappedUSDC + default: + errorHandler(c, fmt.Errorf("token not recognized: %s", tokenReq), faucetServer.Logger) + return + } + + // make sure there's an address + var req requestAddr + if err := c.Bind(&req); err != nil { + errorHandler(c, fmt.Errorf("unable to parse request: %w", err), faucetServer.Logger) + return + } + + // make sure the address is valid + if !common.IsHexAddress(req.Address) { + errorHandler(c, fmt.Errorf("unexpected address %s", req.Address), faucetServer.Logger) + return + } + + // fund the address + addr := common.HexToAddress(req.Address) + if err := faucetServer.Fund(&addr, token, defaultAmount); err != nil { + errorHandler(c, fmt.Errorf("unable to fund request %w", err), faucetServer.Logger) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + } +}