mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 18:31:41 +00:00
feat(test): add e2e tests for usdc swap with did
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
package usdcswapdid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sonr-io/sonr/test/e2e/utils"
|
||||
dextypes "github.com/sonr-io/sonr/x/dex/types"
|
||||
didtypes "github.com/sonr-io/sonr/x/did/types"
|
||||
)
|
||||
|
||||
// CreateTestDID creates a DID document for testing
|
||||
// Returns the created DID document or error
|
||||
// Note: For E2E tests, DIDs should be created using CLI commands before running tests
|
||||
// This function checks if the DID exists and returns it, or returns instructions to create it
|
||||
func CreateTestDID(ctx context.Context, cfg *utils.TestConfig, account, didID string) (*didtypes.DIDDocument, error) {
|
||||
// Check if DID already exists
|
||||
existing, err := QueryDID(ctx, cfg, didID)
|
||||
if err == nil && existing != nil {
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
// DID doesn't exist - return error with instructions
|
||||
return nil, fmt.Errorf("DID %s not found. Create it using CLI:\n"+
|
||||
" snrd tx did create-did %s --from %s --chain-id %s --yes",
|
||||
didID, didID, account, cfg.ChainID)
|
||||
}
|
||||
|
||||
// QueryDID queries a DID document by ID
|
||||
func QueryDID(ctx context.Context, cfg *utils.TestConfig, didID string) (*didtypes.DIDDocument, error) {
|
||||
// Build query URL
|
||||
url := fmt.Sprintf("%s/sonr/did/v1/did/%s", cfg.BaseURL, didID)
|
||||
|
||||
// Response structure
|
||||
var response struct {
|
||||
DidDocument *didtypes.DIDDocument `json:"did_document"`
|
||||
}
|
||||
|
||||
// Make HTTP request
|
||||
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to query DID: %w", err)
|
||||
}
|
||||
|
||||
return response.DidDocument, nil
|
||||
}
|
||||
|
||||
// QueryDEXAccount queries a DEX account by DID and connection ID
|
||||
func QueryDEXAccount(ctx context.Context, cfg *utils.TestConfig, didID, connectionID string) (*dextypes.InterchainDEXAccount, error) {
|
||||
// Build query URL
|
||||
url := fmt.Sprintf("%s/sonr/dex/v1/account/%s/%s", cfg.BaseURL, didID, connectionID)
|
||||
|
||||
// Response structure
|
||||
var response struct {
|
||||
Account *dextypes.InterchainDEXAccount `json:"account"`
|
||||
}
|
||||
|
||||
// Make HTTP request
|
||||
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to query DEX account: %w", err)
|
||||
}
|
||||
|
||||
return response.Account, nil
|
||||
}
|
||||
|
||||
// QueryDEXHistory queries transaction history for a DID
|
||||
func QueryDEXHistory(ctx context.Context, cfg *utils.TestConfig, didID string) ([]*dextypes.DEXActivity, error) {
|
||||
// Build query URL
|
||||
url := fmt.Sprintf("%s/sonr/dex/v1/history/%s", cfg.BaseURL, didID)
|
||||
|
||||
// Response structure
|
||||
var response struct {
|
||||
History []*dextypes.DEXActivity `json:"history"`
|
||||
}
|
||||
|
||||
// Make HTTP request
|
||||
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to query DEX history: %w", err)
|
||||
}
|
||||
|
||||
return response.History, nil
|
||||
}
|
||||
|
||||
// QueryAllDEXAccounts queries all DEX accounts
|
||||
func QueryAllDEXAccounts(ctx context.Context, cfg *utils.TestConfig) ([]*dextypes.InterchainDEXAccount, error) {
|
||||
// Build query URL
|
||||
url := fmt.Sprintf("%s/sonr/dex/v1/accounts", cfg.BaseURL)
|
||||
|
||||
// Response structure
|
||||
var response struct {
|
||||
Accounts []*dextypes.InterchainDEXAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
// Make HTTP request
|
||||
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to query all DEX accounts: %w", err)
|
||||
}
|
||||
|
||||
return response.Accounts, nil
|
||||
}
|
||||
|
||||
// WaitForDEXAccountActivation waits for a DEX account to become active
|
||||
// Returns true if account is active, false if timeout
|
||||
func WaitForDEXAccountActivation(ctx context.Context, cfg *utils.TestConfig, didID, connectionID string, timeout time.Duration) (bool, error) {
|
||||
deadline := time.Now().Add(timeout)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
account, err := QueryDEXAccount(ctx, cfg, didID, connectionID)
|
||||
if err != nil {
|
||||
// Account not found yet, continue waiting
|
||||
time.Sleep(cfg.BlockTime)
|
||||
continue
|
||||
}
|
||||
|
||||
if account.Status == dextypes.ACCOUNT_STATUS_ACTIVE {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check for failed status
|
||||
if account.Status == dextypes.ACCOUNT_STATUS_FAILED {
|
||||
return false, fmt.Errorf("DEX account activation failed")
|
||||
}
|
||||
|
||||
time.Sleep(cfg.BlockTime)
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("timeout waiting for account activation")
|
||||
}
|
||||
|
||||
// VerifyICAAccountAddress verifies that an ICA account has been created
|
||||
func VerifyICAAccountAddress(ctx context.Context, cfg *utils.TestConfig, didID, connectionID string) (string, error) {
|
||||
account, err := QueryDEXAccount(ctx, cfg, didID, connectionID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to query DEX account: %w", err)
|
||||
}
|
||||
|
||||
if account.AccountAddress == "" {
|
||||
return "", fmt.Errorf("ICA account address not yet assigned")
|
||||
}
|
||||
|
||||
return account.AccountAddress, nil
|
||||
}
|
||||
|
||||
// ValidateSwapParameters validates swap parameters before execution
|
||||
func ValidateSwapParameters(sourceDenom, targetDenom string, amount, minOut int64) error {
|
||||
if sourceDenom == "" {
|
||||
return fmt.Errorf("source denom cannot be empty")
|
||||
}
|
||||
|
||||
if targetDenom == "" {
|
||||
return fmt.Errorf("target denom cannot be empty")
|
||||
}
|
||||
|
||||
if sourceDenom == targetDenom {
|
||||
return fmt.Errorf("source and target denoms must be different")
|
||||
}
|
||||
|
||||
if amount <= 0 {
|
||||
return fmt.Errorf("swap amount must be positive")
|
||||
}
|
||||
|
||||
if minOut < 0 {
|
||||
return fmt.Errorf("min output cannot be negative")
|
||||
}
|
||||
|
||||
if minOut > amount {
|
||||
return fmt.Errorf("min output cannot exceed input amount (unless exchange rate > 1)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalculateMinimumOutput calculates minimum output based on slippage tolerance
|
||||
// slippagePct should be between 0 and 100 (e.g., 5 for 5% slippage)
|
||||
func CalculateMinimumOutput(inputAmount int64, slippagePct float64) int64 {
|
||||
if slippagePct < 0 {
|
||||
slippagePct = 0
|
||||
}
|
||||
if slippagePct > 100 {
|
||||
slippagePct = 100
|
||||
}
|
||||
|
||||
multiplier := (100 - slippagePct) / 100
|
||||
return int64(float64(inputAmount) * multiplier)
|
||||
}
|
||||
|
||||
// FormatUSDCAmount formats a USDC amount for display
|
||||
// USDC has 6 decimals, so 1000000 = 1.000000 USDC
|
||||
func FormatUSDCAmount(amount int64) string {
|
||||
whole := amount / 1_000_000
|
||||
fractional := amount % 1_000_000
|
||||
return fmt.Sprintf("%d.%06d USDC", whole, fractional)
|
||||
}
|
||||
|
||||
// FormatSNRAmount formats a SNR amount for display
|
||||
// SNR has 6 decimals (usnr), so 1000000 = 1.000000 SNR
|
||||
func FormatSNRAmount(amount int64) string {
|
||||
whole := amount / 1_000_000
|
||||
fractional := amount % 1_000_000
|
||||
return fmt.Sprintf("%d.%06d SNR", whole, fractional)
|
||||
}
|
||||
|
||||
// ParseIBCDenom parses an IBC denom to extract the base denom
|
||||
// IBC denoms have format: ibc/HASH
|
||||
func ParseIBCDenom(ibcDenom string) (hash string, isIBC bool) {
|
||||
if len(ibcDenom) > 4 && ibcDenom[:4] == "ibc/" {
|
||||
return ibcDenom[4:], true
|
||||
}
|
||||
return ibcDenom, false
|
||||
}
|
||||
|
||||
// BuildNobleIBCDenom builds the IBC denom for Noble USDC on Sonr chain
|
||||
// This requires knowing the IBC channel from Noble to Sonr
|
||||
func BuildNobleIBCDenom(channelID string) string {
|
||||
// In actual implementation, this would use IBC denom trace hashing
|
||||
// For now, return a placeholder
|
||||
return fmt.Sprintf("ibc/noble_usdc_via_%s", channelID)
|
||||
}
|
||||
|
||||
// ExtractSwapEventData extracts relevant data from a swap event
|
||||
type SwapEventData struct {
|
||||
DID string
|
||||
ConnectionID string
|
||||
SourceDenom string
|
||||
TargetDenom string
|
||||
Amount string
|
||||
MinAmountOut string
|
||||
Sequence string
|
||||
SwapType string
|
||||
ICAAddress string
|
||||
}
|
||||
|
||||
// ParseSwapEvent parses a swap event from transaction logs
|
||||
func ParseSwapEvent(events []interface{}) (*SwapEventData, error) {
|
||||
// This would parse the actual event structure from the transaction
|
||||
// For now, return a placeholder
|
||||
return &SwapEventData{}, fmt.Errorf("event parsing not yet implemented")
|
||||
}
|
||||
|
||||
// VerifyNobleConnection verifies that a Noble IBC connection exists
|
||||
func VerifyNobleConnection(ctx context.Context, cfg *utils.TestConfig, connectionID string) error {
|
||||
// Build query URL for connection
|
||||
url := fmt.Sprintf("%s/ibc/core/connection/v1/connections/%s", cfg.BaseURL, connectionID)
|
||||
|
||||
var response struct {
|
||||
Connection struct {
|
||||
State string `json:"state"`
|
||||
} `json:"connection"`
|
||||
}
|
||||
|
||||
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||||
return fmt.Errorf("failed to query connection: %w", err)
|
||||
}
|
||||
|
||||
if response.Connection.State != "STATE_OPEN" {
|
||||
return fmt.Errorf("connection is not open: %s", response.Connection.State)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user