Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refund scripts #579

Merged
merged 17 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ build/
/utils/*
/**/.env.regtest
/**/gh_token.txt
/**/geth_keystore/
/**/local/cookie_jar.txt
!sample-config.env
/**/*.env
Expand Down
16 changes: 12 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: test all clean
.PHONY: test all clean utils

COVER_FILE = coverage/cover.out
TEMPORAL_COVER_FILE =$(shell pwd)/coverage/cover.out.temp
Expand Down Expand Up @@ -63,6 +63,14 @@ test: clean
clean:
rm -rf build $(TEMPORAL_COVER_FILE)

utils:
mkdir -p utils && cd utils
CGO_ENABLED=0 go build -v -o ./utils/update_provider_url ./cmd/utils/update_provider_url.go
utils: download
rm -rf utils
mkdir -p utils
CGO_ENABLED=0 go build -v -o ./utils/update_provider_url ./cmd/utils/update_provider_url/update_provider_url.go
CGO_ENABLED=0 go build -v -o ./utils/register_pegin ./cmd/utils/register_pegin/register_pegin.go
CGO_ENABLED=0 go build -v -o ./utils/refund_user_pegout ./cmd/utils/refund_user_pegout/refund_user_pegout.go

utils-docker:
rm -rf utils
mkdir -p utils
docker build -f docker-compose/utils/Dockerfile --output=utils .
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ mentioned before are the minimal dependencies, but in order to run a fully funct
The [cmd/utils](cmd/utils) directory contains scripts with different utilities for the liquidity providers. You can either run them directly
with `go run` or build them with `make utils`. You can run the scripts with the `--help` flag to see the available options. The current utilities are:
- **update_provider_url**: updates the URL of a liquidity provider provided when the discovery function of the Liquidity Bridge Contract is executed.
- **register_pegin**: register a PegIn transaction within the Liquidity Bridge Contract. Most times, this script is only required to execute refunds
on special cases. This script requires an input file whose structure can be found [the input-example.json](cmd/utils/register_pegin/input-example.json) file.
- **refund_user_pegout**: executes a refund for a user's peg-out operation through the Liquidity Bridge Contract. This is used when a peg-out operation needs to be refunded back to the user's RSK address. The script requires the quote hash of the operation to refund.

### More information
If you're looking forward to integrate with Flyover Protocol then you can check the [Flyover SDK repository](https://github.com/rsksmart/unified-bridges-sdk/tree/main/packages/flyover-sdk).
Expand Down
76 changes: 76 additions & 0 deletions cmd/utils/refund_user_pegout/refund_user_pegout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package main

import (
"context"
"flag"
"fmt"

"github.com/go-playground/validator/v10"
"github.com/rsksmart/liquidity-provider-server/cmd/utils/scripts"
"github.com/rsksmart/liquidity-provider-server/internal/configuration/bootstrap"
"github.com/rsksmart/liquidity-provider-server/internal/configuration/environment"
"github.com/rsksmart/liquidity-provider-server/internal/entities/blockchain"
"golang.org/x/term"
)

type RefundUserPegOutScriptInput struct {
scripts.BaseInput // Embedding BaseInput
QuoteHashBytes string `validate:"required,hexadecimal"`
}

type PasswordReader = func(int) ([]byte, error)

func main() {
scripts.SetUsageMessage(
"This script is used to execute a refund for a PegOut transaction in the Liquidity Bridge Contract." +
" It is intended for use when the final user does not receive their funds." +
" To perform this refund, you must provide the hash of the quote agreed for the service.",
)
scriptInput := new(RefundUserPegOutScriptInput)
ReadRefundUserPegOutScriptInput(scriptInput)
env, err := ParseRefundUserPegOutScriptInput(flag.Parse, scriptInput, term.ReadPassword)
if err != nil {
scripts.ExitWithError(2, "Error reading input", err)
}

ctx := context.Background()
lbc, err := scripts.CreateLiquidityBridgeContract(ctx, bootstrap.Rootstock, env)
if err != nil {
scripts.ExitWithError(2, "Error accessing the Liquidity Bridge Contract", err)
}

txHash, err := ExecuteRefundUserPegOut(lbc, scriptInput.QuoteHashBytes)
if err != nil {
scripts.ExitWithError(2, "Error on transaction execution", err)
}
fmt.Println("Refund user peg out executed successfully. Transaction hash: ", txHash)
}

func ReadRefundUserPegOutScriptInput(scriptInput *RefundUserPegOutScriptInput) {
flag.StringVar(&scriptInput.Network, "network", "", "The network to execute the script. Must be one of the following: regtest, testnet, mainnet")
flag.StringVar(&scriptInput.QuoteHashBytes, "quote-hash", "", "The quote hash to refund the user peg out")

flag.StringVar(&scriptInput.AwsLocalEndpoint, "aws-endpoint", "http://localhost:4566", "AWS endpoint for localstack")
flag.StringVar(&scriptInput.SecretSource, "secret-src", "", "The source of the secrets to execute the transaction. Must be one of the following: env, aws")
flag.StringVar(&scriptInput.RskEndpoint, "rsk-endpoint", "", "The URL of the RSK RPC server. E.g. http://localhost:4444")
flag.StringVar(&scriptInput.CustomLbcAddress, "lbc-address", "", "Custom address of the liquidity bridge contract. If not provided will use the network default.")

flag.StringVar(&scriptInput.KeystoreFile, "keystore-file", "", "Path to the keystore file. Only required if the secret source is env")
flag.StringVar(&scriptInput.EncryptedJsonSecret, "keystore-secret", "", "Name of the secret storing the keystore. Only required if the secret source is aws")
flag.StringVar(&scriptInput.EncryptedJsonPasswordSecret, "password-secret", "", "Name of the secret storing the keystore password. Only required if the secret source is aws")
}

func ParseRefundUserPegOutScriptInput(parse scripts.ParseFunc, scriptInput *RefundUserPegOutScriptInput, pwdReader PasswordReader) (environment.Environment, error) {
parse()
validate := validator.New(validator.WithRequiredStructEnabled())
err := validate.Struct(scriptInput)
if err != nil {
return environment.Environment{}, fmt.Errorf("invalid input: %w", err)
}

return scriptInput.BaseInput.ToEnv(pwdReader)
}

func ExecuteRefundUserPegOut(lbc blockchain.LiquidityBridgeContract, quoteHash string) (string, error) {
return lbc.RefundUserPegOut(quoteHash)
}
108 changes: 108 additions & 0 deletions cmd/utils/refund_user_pegout/refund_user_pegout_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package main

import (
"flag"
"testing"

"github.com/rsksmart/liquidity-provider-server/test"
"github.com/rsksmart/liquidity-provider-server/test/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/term"

"github.com/rsksmart/liquidity-provider-server/cmd/utils/scripts"
)

const (
testRskEndpoint = "http://localhost:4444"
testQuoteHash = "d93f58c82100a6cee4f19ac505c11d51b52cafe220f7f1944b70496f33d277fc"
testAwsLocalEndpoint = "http://localhost:4566"
testNetwork = "regtest"
testKeystoreFile = "./keystore.json"
)

func TestReadRefundUserPegOutScriptInput(t *testing.T) {
t.Run("should set flag values", func(t *testing.T) {
// Reset flags before test
flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)

scriptInput := new(RefundUserPegOutScriptInput)
ReadRefundUserPegOutScriptInput(scriptInput)

// Set test values
err := flag.CommandLine.Parse([]string{
"-network", testNetwork,
"-quote-hash", testQuoteHash,
"-rsk-endpoint", testRskEndpoint,
"-secret-src", "env",
"-keystore-file", testKeystoreFile,
})
require.NoError(t, err)

assert.Equal(t, testNetwork, scriptInput.Network)
assert.Equal(t, testQuoteHash, scriptInput.QuoteHashBytes)
assert.Equal(t, testRskEndpoint, scriptInput.RskEndpoint)
assert.Equal(t, "env", scriptInput.SecretSource)
assert.Equal(t, testKeystoreFile, scriptInput.KeystoreFile)
})
}

func TestParseRefundUserPegOutScriptInput(t *testing.T) {

parse := func() { // parse is a no-op function used as a placeholder in tests since the actual parsing
// functionality is not relevant for these test cases
}

t.Run("should validate required fields", func(t *testing.T) {
scriptInput := &RefundUserPegOutScriptInput{
BaseInput: scripts.BaseInput{
Network: "",
RskEndpoint: "",
SecretSource: "",
},
QuoteHashBytes: "",
}

_, err := ParseRefundUserPegOutScriptInput(parse, scriptInput, term.ReadPassword)
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid input")
})

t.Run("should parse valid input", func(t *testing.T) {
scriptInput := &RefundUserPegOutScriptInput{
BaseInput: scripts.BaseInput{
Network: testNetwork,
RskEndpoint: testRskEndpoint,
SecretSource: "aws",
AwsLocalEndpoint: testAwsLocalEndpoint,
},
QuoteHashBytes: testQuoteHash,
}

env, err := ParseRefundUserPegOutScriptInput(parse, scriptInput, func(fd int) ([]byte, error) {
return []byte("password"), nil
})
require.NoError(t, err)
assert.Equal(t, testNetwork, env.LpsStage)
assert.Equal(t, testRskEndpoint, env.Rsk.Endpoint)
assert.Equal(t, "aws", env.SecretSource)
assert.Equal(t, testAwsLocalEndpoint, env.AwsLocalEndpoint)
})
}

func TestRefundUserPegOut(t *testing.T) {
t.Run("should execute refund user peg out successfully", func(t *testing.T) {
lbc := &mocks.LbcMock{}
expectedTxHash := test.AnyHash

// Setup mock expectations
lbc.On("RefundUserPegOut", testQuoteHash).Return(expectedTxHash, nil)

txHash, err := ExecuteRefundUserPegOut(lbc, testQuoteHash)
require.NoError(t, err)
assert.Equal(t, expectedTxHash, txHash)

// Verify all expectations were met
lbc.AssertExpectations(t)
})
}
26 changes: 26 additions & 0 deletions cmd/utils/register_pegin/input-example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"quote": {
"fedBTCAddr": "2N5muMepJizJE1gR7FbHJU6CD18V3BpNF9p",
"lbcAddr": "0x7557fcE0BbFAe81a9508FF469D481f2c72a8B5f3",
"lpRSKAddr": "0x9d93929a9099be4355fc2389fbf253982f9df47c",
"btcRefundAddr": "mfWxJ45yp2SFn7UciZyNpvDKrzbhyfKrY8",
"rskRefundAddr": "0x79568c2989232dCa1840087D73d403602364c0D4",
"lpBTCAddr": "n1jGDaxCW6jemLZyd9wmDHddseZwEMV9C6",
"callFee": 10000000000000000,
"penaltyFee": 1000000000000000,
"contractAddr": "0x79568c2989232dCa1840087D73d403602364c0D4",
"data": "",
"gasLimit": 46000,
"nonce": 8941842587185974000,
"value": 600000000000000000,
"agreementTimestamp": 1732101992,
"timeForDeposit": 3600,
"lpCallTime": 7200,
"confirmations": 10,
"callOnRegister": false,
"gasFee": 0,
"productFeeAmount": 0
},
"signature": "7290e2c28751d7e4ba2ea5fe5f8b1d3a0bfcd55089fddc0e74fe6809afb8195622801d2dd8267ea3cc4088f5e4b133e0e22dcc403ee0f838efbb277f493c8cde1b",
"btcTxHash": "e57767cefb13bb962e9729d99adbb7147f6054af6e8f4d7c4cd47e74cf9ccaa4"
}
Loading
Loading