2025-10-03 14:45:52 -04:00
|
|
|
package keeper
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
2025-10-10 11:47:18 -04:00
|
|
|
"github.com/sonr-io/crypto/keys"
|
|
|
|
|
"github.com/sonr-io/crypto/ucan"
|
2025-10-03 14:45:52 -04:00
|
|
|
"github.com/sonr-io/sonr/x/dex/types"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// PermissionValidator wraps UCAN verifier for DEX-specific permission validation
|
|
|
|
|
type PermissionValidator struct {
|
|
|
|
|
verifier *ucan.Verifier
|
|
|
|
|
keeper Keeper
|
|
|
|
|
permissions *types.UCANPermissionRegistry
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewPermissionValidator creates a new DEX permission validator
|
|
|
|
|
func NewPermissionValidator(keeper Keeper) *PermissionValidator {
|
|
|
|
|
didResolver := &DEXDIDResolver{keeper: keeper}
|
|
|
|
|
verifier := ucan.NewVerifier(didResolver)
|
|
|
|
|
|
|
|
|
|
return &PermissionValidator{
|
|
|
|
|
verifier: verifier,
|
|
|
|
|
keeper: keeper,
|
|
|
|
|
permissions: types.NewUCANPermissionRegistry(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidatePermission validates UCAN token for DEX operation
|
|
|
|
|
func (pv *PermissionValidator) ValidatePermission(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
tokenString string,
|
|
|
|
|
resourceType string,
|
|
|
|
|
resourceID string,
|
|
|
|
|
operation types.DEXOperation,
|
|
|
|
|
) error {
|
|
|
|
|
// Get required UCAN capabilities for the operation
|
|
|
|
|
capabilities, err := pv.permissions.GetRequiredUCANCapabilities(operation)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get required UCAN capabilities: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build resource URI for DEX
|
|
|
|
|
mapper := types.NewUCANCapabilityMapper()
|
|
|
|
|
resourceURI := mapper.CreateDEXResourceURI(resourceType, resourceID)
|
|
|
|
|
|
|
|
|
|
// Verify UCAN token grants required capabilities
|
|
|
|
|
_, err = pv.verifier.VerifyCapability(
|
|
|
|
|
ctx,
|
|
|
|
|
tokenString,
|
|
|
|
|
resourceURI,
|
|
|
|
|
capabilities,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("UCAN validation failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidateSwapPermission validates UCAN token for swap operations
|
|
|
|
|
func (pv *PermissionValidator) ValidateSwapPermission(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
tokenString string,
|
|
|
|
|
poolID string,
|
|
|
|
|
amount string,
|
|
|
|
|
operation types.DEXOperation,
|
|
|
|
|
) error {
|
|
|
|
|
// Get required UCAN capabilities for the operation
|
|
|
|
|
capabilities, err := pv.permissions.GetRequiredUCANCapabilities(operation)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get required UCAN capabilities: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build pool resource URI
|
|
|
|
|
mapper := types.NewUCANCapabilityMapper()
|
|
|
|
|
resourceURI := mapper.CreatePoolResourceURI(poolID)
|
|
|
|
|
|
|
|
|
|
// Verify UCAN token
|
|
|
|
|
token, err := pv.verifier.VerifyCapability(
|
|
|
|
|
ctx,
|
|
|
|
|
tokenString,
|
|
|
|
|
resourceURI,
|
|
|
|
|
capabilities,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("UCAN validation failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Additional amount validation
|
|
|
|
|
if err := pv.validateAmountConstraint(token, amount); err != nil {
|
|
|
|
|
return fmt.Errorf("amount constraint validation failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidateLiquidityPermission validates UCAN token for liquidity operations
|
|
|
|
|
func (pv *PermissionValidator) ValidateLiquidityPermission(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
tokenString string,
|
|
|
|
|
poolID string,
|
|
|
|
|
operation types.DEXOperation,
|
|
|
|
|
) error {
|
|
|
|
|
// Get required UCAN capabilities for the operation
|
|
|
|
|
capabilities, err := pv.permissions.GetRequiredUCANCapabilities(operation)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get required UCAN capabilities: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build pool resource URI
|
|
|
|
|
mapper := types.NewUCANCapabilityMapper()
|
|
|
|
|
resourceURI := mapper.CreatePoolResourceURI(poolID)
|
|
|
|
|
|
|
|
|
|
// Verify UCAN token
|
|
|
|
|
_, err = pv.verifier.VerifyCapability(
|
|
|
|
|
ctx,
|
|
|
|
|
tokenString,
|
|
|
|
|
resourceURI,
|
|
|
|
|
capabilities,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("UCAN validation failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidateOrderPermission validates UCAN token for order operations
|
|
|
|
|
func (pv *PermissionValidator) ValidateOrderPermission(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
tokenString string,
|
|
|
|
|
orderID string,
|
|
|
|
|
operation types.DEXOperation,
|
|
|
|
|
) error {
|
|
|
|
|
// Get required UCAN capabilities for the operation
|
|
|
|
|
capabilities, err := pv.permissions.GetRequiredUCANCapabilities(operation)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get required UCAN capabilities: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build order resource URI
|
|
|
|
|
mapper := types.NewUCANCapabilityMapper()
|
|
|
|
|
resourceURI := mapper.CreateOrderResourceURI(orderID)
|
|
|
|
|
|
|
|
|
|
// Verify UCAN token
|
|
|
|
|
_, err = pv.verifier.VerifyCapability(
|
|
|
|
|
ctx,
|
|
|
|
|
tokenString,
|
|
|
|
|
resourceURI,
|
|
|
|
|
capabilities,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("UCAN validation failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// VerifyDelegationChain validates complete UCAN delegation chain
|
|
|
|
|
func (pv *PermissionValidator) VerifyDelegationChain(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
tokenString string,
|
|
|
|
|
) error {
|
|
|
|
|
return pv.verifier.VerifyDelegationChain(ctx, tokenString)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Internal validation methods
|
|
|
|
|
|
|
|
|
|
// validateAmountConstraint validates amount constraints
|
|
|
|
|
func (pv *PermissionValidator) validateAmountConstraint(
|
|
|
|
|
token *ucan.Token,
|
|
|
|
|
amount string,
|
|
|
|
|
) error {
|
|
|
|
|
// For now, we'll accept all amounts
|
|
|
|
|
// In a real implementation, we'd check against maximum amounts
|
|
|
|
|
// specified in the token's attenuations
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// validatePoolConstraint validates pool constraints
|
|
|
|
|
func (pv *PermissionValidator) validatePoolConstraint(
|
|
|
|
|
token *ucan.Token,
|
|
|
|
|
poolID string,
|
|
|
|
|
) error {
|
|
|
|
|
// Check if the token's resource matches the pool
|
|
|
|
|
for _, att := range token.Attenuations {
|
|
|
|
|
if simpleResource, ok := att.Resource.(*ucan.SimpleResource); ok {
|
|
|
|
|
// Check if resource matches pool pattern
|
|
|
|
|
if simpleResource.Scheme == "dex" {
|
|
|
|
|
expectedValue := fmt.Sprintf("pool:%s", poolID)
|
|
|
|
|
if simpleResource.Value == expectedValue || simpleResource.Value == "pool:*" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("no matching pool attenuation found for pool %s", poolID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper methods
|
|
|
|
|
|
|
|
|
|
// CreateAttenuation creates a UCAN attenuation for DEX operations
|
|
|
|
|
func (pv *PermissionValidator) CreateAttenuation(
|
|
|
|
|
actions []string,
|
|
|
|
|
resourceType string,
|
|
|
|
|
resourceID string,
|
|
|
|
|
) ucan.Attenuation {
|
|
|
|
|
return pv.permissions.CreateDEXAttenuation(actions, resourceType, resourceID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreateAmountLimitedAttenuation creates an amount-limited UCAN attenuation
|
|
|
|
|
func (pv *PermissionValidator) CreateAmountLimitedAttenuation(
|
|
|
|
|
actions []string,
|
|
|
|
|
poolID string,
|
|
|
|
|
maxAmount string,
|
|
|
|
|
) ucan.Attenuation {
|
|
|
|
|
return pv.permissions.CreateAmountLimitedAttenuation(actions, poolID, maxAmount)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreatePoolRestrictedAttenuation creates a pool-restricted UCAN attenuation
|
|
|
|
|
func (pv *PermissionValidator) CreatePoolRestrictedAttenuation(
|
|
|
|
|
actions []string,
|
|
|
|
|
allowedPools []string,
|
|
|
|
|
) ucan.Attenuation {
|
|
|
|
|
return pv.permissions.CreatePoolRestrictedAttenuation(actions, allowedPools)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DEXDIDResolver implements ucan.DIDResolver for DEX module
|
|
|
|
|
type DEXDIDResolver struct {
|
|
|
|
|
keeper Keeper
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ResolveDIDKey resolves DID to public key for UCAN verification
|
|
|
|
|
func (r *DEXDIDResolver) ResolveDIDKey(ctx context.Context, did string) (keys.DID, error) {
|
|
|
|
|
// For DEX module, we need to resolve DIDs from the DID module
|
|
|
|
|
// This would require cross-module keeper access
|
|
|
|
|
|
|
|
|
|
// Check if the DEX keeper has access to DID keeper
|
|
|
|
|
if r.keeper.didKeeper != nil {
|
|
|
|
|
didDoc, err := r.keeper.didKeeper.GetDIDDocument(ctx, did)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return keys.DID{}, fmt.Errorf("failed to get DID document: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if didDoc == nil {
|
|
|
|
|
return keys.DID{}, fmt.Errorf("DID document not found")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse the DID string into a keys.DID
|
|
|
|
|
return keys.Parse(did)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return keys.DID{}, fmt.Errorf("DID resolver not available in DEX module")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Gasless transaction support
|
|
|
|
|
|
|
|
|
|
// SupportsGaslessTransaction checks if a UCAN token supports gasless transactions
|
|
|
|
|
func (pv *PermissionValidator) SupportsGaslessTransaction(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
tokenString string,
|
|
|
|
|
poolID string,
|
|
|
|
|
operation types.DEXOperation,
|
|
|
|
|
) (bool, uint64, error) {
|
|
|
|
|
// Parse and verify the token
|
|
|
|
|
token, err := pv.verifier.VerifyToken(ctx, tokenString)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, 0, fmt.Errorf("token verification failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mapper := types.NewUCANCapabilityMapper()
|
|
|
|
|
resourceURI := mapper.CreatePoolResourceURI(poolID)
|
|
|
|
|
|
|
|
|
|
// Check each attenuation for gasless support
|
|
|
|
|
for _, att := range token.Attenuations {
|
|
|
|
|
if att.Resource.GetURI() == resourceURI {
|
|
|
|
|
// Check if capability supports gasless transactions
|
|
|
|
|
if gaslessCapability, ok := att.Capability.(*ucan.GaslessCapability); ok {
|
|
|
|
|
if gaslessCapability.SupportsGasless() {
|
|
|
|
|
// Verify the capability grants the required operation
|
|
|
|
|
capabilities, err := pv.permissions.GetRequiredUCANCapabilities(operation)
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if gaslessCapability.Grants(capabilities) {
|
|
|
|
|
return true, gaslessCapability.GetGasLimit(), nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false, 0, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidateRateLimit checks if a UCAN token has rate limiting and if it's within limits
|
|
|
|
|
func (pv *PermissionValidator) ValidateRateLimit(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
tokenString string,
|
|
|
|
|
poolID string,
|
|
|
|
|
) (bool, uint64, uint64, error) {
|
|
|
|
|
// Parse and verify the token
|
|
|
|
|
token, err := pv.verifier.VerifyToken(ctx, tokenString)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, 0, 0, fmt.Errorf("token verification failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mapper := types.NewUCANCapabilityMapper()
|
|
|
|
|
resourceURI := mapper.CreatePoolResourceURI(poolID)
|
|
|
|
|
|
|
|
|
|
// Check each attenuation for rate limiting
|
|
|
|
|
for _, att := range token.Attenuations {
|
|
|
|
|
if att.Resource.GetURI() == resourceURI {
|
|
|
|
|
// Check if this is a gasless capability with limits
|
|
|
|
|
if gaslessCapability, ok := att.Capability.(*ucan.GaslessCapability); ok {
|
|
|
|
|
if gaslessCapability.AllowGasless && gaslessCapability.GasLimit > 0 {
|
|
|
|
|
// Use gas limit as a proxy for rate limiting
|
|
|
|
|
return true, gaslessCapability.GasLimit, 60, nil // 60 second window
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false, 0, 0, nil
|
|
|
|
|
}
|