Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion paymaster/build_txn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var STRKContractAddress, _ = internalUtils.HexToFelt(

// Test the UserTxnType type
//

//nolint:dupl // The enum tests are similar, but with different enum values
func TestUserTxnType(t *testing.T) {
tests.RunTestOn(t, tests.MockEnv)
t.Parallel()
Expand Down
37 changes: 37 additions & 0 deletions paymaster/get_tokens.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package paymaster

import (
"context"

"github.com/NethermindEth/juno/core/felt"
)

// Get a list of the tokens that the paymaster supports, together with their prices in STRK
//
// Parameters:
// - ctx: The context.Context object for controlling the function call
//
// Returns:
// - []TokenData: An array of token data
// - error: An error if any
func (p *Paymaster) GetSupportedTokens(ctx context.Context) ([]TokenData, error) {
var response []TokenData
if err := p.c.CallContextWithSliceArgs(
ctx, &response, "paymaster_getSupportedTokens",
); err != nil {
return nil, err
}

return response, nil
}

// Object containing data about the token: contract address, number of
// decimals and current price in STRK
type TokenData struct {
// Token contract address
TokenAddress *felt.Felt `json:"token_address"`
// The number of decimals of the token
Decimals uint8 `json:"decimals"`
// Price in STRK (in FRI units)
PriceInStrk string `json:"price_in_strk"` // u256 as a hex string
}
75 changes: 75 additions & 0 deletions paymaster/get_tokens_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package paymaster

import (
"context"
"encoding/json"
"testing"

"github.com/NethermindEth/starknet.go/internal/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)

// Test the 'paymaster_getSupportedTokens' method
func TestGetSupportedTokens(t *testing.T) {
t.Parallel()
t.Run("integration", func(t *testing.T) {
tests.RunTestOn(t, tests.IntegrationEnv)
t.Parallel()

pm, spy := SetupPaymaster(t)
tokens, err := pm.GetSupportedTokens(context.Background())
require.NoError(t, err)

rawResult, err := json.Marshal(tokens)
require.NoError(t, err)
assert.EqualValues(t, spy.LastResponse(), rawResult)
})

t.Run("mock", func(t *testing.T) {
tests.RunTestOn(t, tests.MockEnv)
t.Parallel()

pm := SetupMockPaymaster(t)

expectedRawResult := `[
{
"token_address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"decimals": 18,
"price_in_strk": "0x288aa92ed8c5539ae80"
},
{
"token_address": "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
"decimals": 18,
"price_in_strk": "0xde0b6b3a7640000"
},
{
"token_address": "0x53b40a647cedfca6ca84f542a0fe36736031905a9639a7f19a3c1e66bfd5080",
"decimals": 6,
"price_in_strk": "0x48e1ecdbbe883b08"
},
{
"token_address": "0x30058f19ed447208015f6430f0102e8ab82d6c291566d7e73fe8e613c3d2ed",
"decimals": 6,
"price_in_strk": "0x2c3460a7992f8a"
}
]`

var expectedResult []TokenData
err := json.Unmarshal([]byte(expectedRawResult), &expectedResult)
require.NoError(t, err)

pm.c.EXPECT().
CallContextWithSliceArgs(context.Background(), gomock.AssignableToTypeOf(new([]TokenData)), "paymaster_getSupportedTokens").
SetArg(1, expectedResult).
Return(nil)
result, err := pm.GetSupportedTokens(context.Background())
assert.NoError(t, err)
assert.Equal(t, expectedResult, result)

rawResult, err := json.Marshal(result)
require.NoError(t, err)
assert.JSONEq(t, expectedRawResult, string(rawResult))
})
}
4 changes: 3 additions & 1 deletion paymaster/paymaster.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"net/http/cookiejar"

"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/starknet.go/client"
"golang.org/x/net/publicsuffix"
)
Expand All @@ -30,8 +31,9 @@ type paymasterInterface interface {
ctx context.Context,
request *ExecuteTransactionRequest,
) (ExecuteTransactionResponse, error)
GetSupportedTokens(ctx context.Context) ([]TokenData, error)
IsAvailable(ctx context.Context) (bool, error)
// More methods coming...
TrackingIDToLatestHash(ctx context.Context, trackingID *felt.Felt) (TrackingIDResponse, error)
}

var _ paymasterInterface = (*Paymaster)(nil)
Expand Down
97 changes: 97 additions & 0 deletions paymaster/tracking_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package paymaster

import (
"context"
"fmt"
"strconv"

"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/starknet.go/client/rpcerr"
)

// TrackingIDToLatestHash gets the latest transaction hash and status for a given tracking ID.
// Returns a TrackingIdResponse.
//
// Parameters:
// - ctx: The context.Context object for controlling the function call
// - trackingID: A unique identifier used to track an execution request of a user.
// This identitifier is returned by the paymaster after a successful call to `execute`.
// Its purpose is to track the possibly different transaction hashes in the mempool which
// are associated with a same user request.
//
// Returns:
// - *TrackingIDResponse: The hash of the latest transaction broadcasted by the paymaster
// corresponding to the requested ID and the status of the ID.
// - error: An error if any
func (p *Paymaster) TrackingIDToLatestHash(
ctx context.Context,
trackingID *felt.Felt,
) (TrackingIDResponse, error) {
var response TrackingIDResponse
if err := p.c.CallContextWithSliceArgs(
ctx,
&response,
"paymaster_trackingIdToLatestHash",
trackingID,
); err != nil {
return TrackingIDResponse{}, rpcerr.UnwrapToRPCErr(err, ErrInvalidID)
}

return response, nil
}

// TrackingIDResponse is the response for the `paymaster_trackingIdToLatestHash` method.
type TrackingIDResponse struct {
// The hash of the most recent tx sent by the paymaster and corresponding to the ID
TransactionHash *felt.Felt `json:"transaction_hash"`
// The status of the transaction associated with the ID
Status TxnStatus `json:"status"`
}

// An enum representing the status of the transaction associated with a tracking ID
type TxnStatus int

const (
// Indicates that the latest transaction associated with the ID is not yet
// included in a block but is still being handled and monitored by the paymaster.
// Represents the "active" string value.
TxnStatusActive TxnStatus = iota + 1
// Indicates that a transaction associated with the ID has been accepted on L2.
// Represents the "accepted" string value.
TxnStatusAccepted
// Indicates that no transaction associated with the ID managed to enter a block
// and the request has been dropped by the paymaster.
// Represents the "dropped" string value.
TxnStatusDropped
)

// String returns the string representation of the TxnStatus.
func (t TxnStatus) String() string {
return []string{"active", "accepted", "dropped"}[t-1]
}

// MarshalJSON marshals the TxnStatus to JSON.
func (t TxnStatus) MarshalJSON() ([]byte, error) {
return strconv.AppendQuote(nil, t.String()), nil
}

// UnmarshalJSON unmarshals the JSON data into a TxnStatus.
func (t *TxnStatus) UnmarshalJSON(b []byte) error {
s, err := strconv.Unquote(string(b))
if err != nil {
return err
}

switch s {
case "active":
*t = TxnStatusActive
case "accepted":
*t = TxnStatusAccepted
case "dropped":
*t = TxnStatusDropped
default:
return fmt.Errorf("invalid transaction status: %s", s)
}

return nil
}
94 changes: 94 additions & 0 deletions paymaster/tracking_id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package paymaster

import (
"context"
"encoding/json"
"testing"

"github.com/NethermindEth/starknet.go/internal/tests"
internalUtils "github.com/NethermindEth/starknet.go/internal/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)

// Test the TxnStatus enum type
//
//nolint:dupl // The enum tests are similar, but with different enum values
func TestTxnStatusType(t *testing.T) {
tests.RunTestOn(t, tests.MockEnv)
t.Parallel()

type testCase struct {
Input string
Expected TxnStatus
ErrorExpected bool
}

testCases := []testCase{
{
Input: `"active"`,
Expected: TxnStatusActive,
ErrorExpected: false,
},
{
Input: `"accepted"`,
Expected: TxnStatusAccepted,
ErrorExpected: false,
},
{
Input: `"dropped"`,
Expected: TxnStatusDropped,
ErrorExpected: false,
},
{
Input: `"unknown"`,
ErrorExpected: true,
},
}

for _, test := range testCases {
t.Run(test.Input, func(t *testing.T) {
t.Parallel()
CompareEnumsHelper(t, test.Input, test.Expected, test.ErrorExpected)
})
}
}

// Test the 'paymaster_trackingIdToLatestHash' method
func TestTrackingIdToLatestHash(t *testing.T) {
// The AVNU paymaster does not support this method yet, so we can't have integration tests
tests.RunTestOn(t, tests.MockEnv)
t.Parallel()

expectedRawResp := `{
"transaction_hash": "0xdeadbeef",
"status": "active"
}`

var expectedResp TrackingIDResponse
err := json.Unmarshal([]byte(expectedRawResp), &expectedResp)
require.NoError(t, err)

trackingID := internalUtils.DeadBeef

pm := SetupMockPaymaster(t)
pm.c.EXPECT().
CallContextWithSliceArgs(
context.Background(),
gomock.AssignableToTypeOf(new(TrackingIDResponse)),
"paymaster_trackingIdToLatestHash",
trackingID,
).
SetArg(1, expectedResp).
Return(nil)

response, err := pm.TrackingIDToLatestHash(context.Background(), trackingID)
require.NoError(t, err)
assert.Equal(t, TxnStatusActive, response.Status)
assert.Equal(t, expectedResp.TransactionHash, response.TransactionHash)

rawResp, err := json.Marshal(response)
require.NoError(t, err)
assert.JSONEq(t, expectedRawResp, string(rawResp))
}
Loading