* clear

* feat: Add everything

* fix: Commenht
This commit is contained in:
Prad Nukala
2025-10-03 14:45:52 -04:00
committed by GitHub
parent 43b4a11c06
commit 13e6c3e84d
1935 changed files with 655061 additions and 40058 deletions
-74
View File
@@ -1,74 +0,0 @@
package app
import (
"errors"
ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante"
"github.com/cosmos/ibc-go/v8/modules/core/keeper"
circuitante "cosmossdk.io/x/circuit/ante"
circuitkeeper "cosmossdk.io/x/circuit/keeper"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
sdkmath "cosmossdk.io/math"
poaante "github.com/strangelove-ventures/poa/ante"
globalfeeante "github.com/strangelove-ventures/globalfee/x/globalfee/ante"
globalfeekeeper "github.com/strangelove-ventures/globalfee/x/globalfee/keeper"
)
// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC
// channel keeper.
type HandlerOptions struct {
ante.HandlerOptions
IBCKeeper *keeper.Keeper
CircuitKeeper *circuitkeeper.Keeper
StakingKeeper *stakingkeeper.Keeper
GlobalFeeKeeper globalfeekeeper.Keeper
BypassMinFeeMsgTypes []string
}
// NewAnteHandler constructor
func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if options.AccountKeeper == nil {
return nil, errors.New("account keeper is required for ante builder")
}
if options.BankKeeper == nil {
return nil, errors.New("bank keeper is required for ante builder")
}
if options.SignModeHandler == nil {
return nil, errors.New("sign mode handler is required for ante builder")
}
if options.CircuitKeeper == nil {
return nil, errors.New("circuit keeper is required for ante builder")
}
poaDoGenTxRateValidation := false
poaRateFloor := sdkmath.LegacyMustNewDecFromStr("0.05")
poaRateCeil := sdkmath.LegacyMustNewDecFromStr("0.25")
anteDecorators := []sdk.AnteDecorator{
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
circuitante.NewCircuitBreakerDecorator(options.CircuitKeeper),
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(options.AccountKeeper),
// ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
globalfeeante.NewFeeDecorator(options.BypassMinFeeMsgTypes, options.GlobalFeeKeeper, 2_000_000),
// ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCKeeper),
poaante.NewPOADisableStakingDecorator(),
poaante.NewCommissionLimitDecorator(poaDoGenTxRateValidation, poaRateFloor, poaRateCeil),
}
return sdk.ChainAnteDecorators(anteDecorators...), nil
}
+60
View File
@@ -0,0 +1,60 @@
// Package ante provides ante handler implementations for transaction processing
// in the Sonr blockchain. It supports both Cosmos SDK and Ethereum transactions.
package ante
import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
)
// NewAnteHandler returns an ante handler responsible for attempting to route an
// Ethereum or SDK transaction to an internal ante handler for performing
// transaction-level processing (e.g. fee payment, signature verification) before
// being passed onto it's respective handler.
//
// The handler inspects the transaction type and extension options to determine
// the appropriate processing path:
// - Ethereum transactions with ExtensionOptionsEthereumTx
// - Cosmos SDK transactions with ExtensionOptionDynamicFeeTx
// - Standard Cosmos SDK transactions
func NewAnteHandler(options HandlerOptions) sdk.AnteHandler {
return func(
ctx sdk.Context, tx sdk.Tx, sim bool,
) (newCtx sdk.Context, err error) {
var anteHandler sdk.AnteHandler
txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx)
if ok {
opts := txWithExtensions.GetExtensionOptions()
if len(opts) > 0 {
switch typeURL := opts[0].GetTypeUrl(); typeURL {
case "/cosmos.evm.vm.v1.ExtensionOptionsEthereumTx":
// handle as *evmtypes.MsgEthereumTx
anteHandler = newMonoEVMAnteHandler(options)
case "/cosmos.evm.vm.v1.ExtensionOptionDynamicFeeTx":
// cosmos-sdk tx with dynamic fee extension
anteHandler = NewCosmosAnteHandler(options)
default:
return ctx, errorsmod.Wrapf(
errortypes.ErrUnknownExtensionOptions,
"rejecting tx with unsupported extension option: %s", typeURL,
)
}
return anteHandler(ctx, tx, sim)
}
}
// handle as totally normal Cosmos SDK tx
switch tx.(type) {
case sdk.Tx:
anteHandler = NewCosmosAnteHandler(options)
default:
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid transaction type: %T", tx)
}
return anteHandler(ctx, tx, sim)
}
}
+107
View File
@@ -0,0 +1,107 @@
package ante
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
evmoscosmosante "github.com/cosmos/evm/ante/cosmos"
evmante "github.com/cosmos/evm/ante/evm"
evmtypes "github.com/cosmos/evm/x/vm/types"
circuitante "cosmossdk.io/x/circuit/ante"
ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante"
)
// NewCosmosAnteHandler creates the default ante handler for Cosmos SDK transactions.
// It sets up a chain of decorators that perform various checks and operations:
// - Rejects Ethereum transactions in Cosmos context
// - Enforces authz limitations
// - Sets up transaction context
// - Validates basic transaction properties
// - Handles WebAuthn gasless transactions
// - Handles gas consumption and fee deduction
// - Performs signature verification
// - Manages account sequences
// - Handles IBC-specific checks
func NewCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler {
// Determine if we should use enhanced gasless mode
// Enhanced mode allows address generation from credentials for true gasless onboarding
enhancedGaslessMode := options.EnableEnhancedGasless
// Build the decorator chain
decorators := []sdk.AnteDecorator{
// WebAuthn bypass - must be first to intercept WebAuthn transactions
NewWebAuthnBypassDecorator(),
evmoscosmosante.NewRejectMessagesDecorator(), // reject MsgEthereumTxs
evmoscosmosante.NewAuthzLimiterDecorator( // disable the Msg types that cannot be included on an authz.MsgExec msgs field
sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}),
sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}),
),
ante.NewSetUpContextDecorator(),
circuitante.NewCircuitBreakerDecorator(options.CircuitKeeper),
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(options.AccountKeeper),
// UCAN validation - must come before fee deduction for gasless support
NewConditionalUCANDecorator(NewUCANDecorator()),
evmoscosmosante.NewMinGasPriceDecorator(
options.FeeMarketKeeper,
options.EvmKeeper,
options.ControlPanelKeeper,
),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
// WebAuthn gasless transaction support - must come before fee deduction
// Enhanced mode allows true gasless onboarding without pre-existing accounts
NewWebAuthnGaslessDecorator(options.AccountKeeper, options.DidKeeper, enhancedGaslessMode),
// Conditional fee deduction - skips fees for gasless WebAuthn and UCAN
NewUCANGaslessDecorator(
NewConditionalFeeDecorator(ante.NewDeductFeeDecorator(
options.AccountKeeper,
options.BankKeeper,
options.FeegrantKeeper,
options.TxFeeChecker,
)),
),
}
// Add signature verification decorators
// In enhanced gasless mode, we wrap these to be conditional
if enhancedGaslessMode {
// Conditional decorators that skip verification for gasless transactions
decorators = append(
decorators,
NewConditionalPubKeyDecorator(ante.NewSetPubKeyDecorator(options.AccountKeeper)),
NewConditionalSigCountDecorator(
ante.NewValidateSigCountDecorator(options.AccountKeeper),
),
NewConditionalSigGasDecorator(
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
),
NewConditionalSignatureDecorator(
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
),
)
} else {
// Standard signature verification decorators
decorators = append(decorators,
ante.NewSetPubKeyDecorator(options.AccountKeeper),
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
)
}
// Add remaining decorators
decorators = append(decorators,
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCKeeper),
evmante.NewGasWantedDecorator(options.EvmKeeper, options.FeeMarketKeeper),
)
return sdk.ChainAnteDecorators(decorators...)
}
+23
View File
@@ -0,0 +1,23 @@
package ante
import (
sdk "github.com/cosmos/cosmos-sdk/types"
evmante "github.com/cosmos/evm/ante/evm"
)
// newMonoEVMAnteHandler creates the ante handler for Ethereum Virtual Machine transactions.
// It uses a single decorator that handles all EVM-specific validation and processing,
// including gas calculation, fee market dynamics, and account management.
//
// The mono decorator performs all EVM ante operations in a single pass for efficiency.
func newMonoEVMAnteHandler(options HandlerOptions) sdk.AnteHandler {
return sdk.ChainAnteDecorators(
evmante.NewEVMMonoDecorator(
options.AccountKeeper,
options.FeeMarketKeeper,
options.EvmKeeper,
options.ControlPanelKeeper,
options.MaxTxGasWanted,
),
)
}
+59
View File
@@ -0,0 +1,59 @@
package ante
import (
"context"
sdk "github.com/cosmos/cosmos-sdk/types"
anteinterfaces "github.com/cosmos/evm/ante/interfaces"
)
// Ensure ControlPanelKeeper implements the interface
var _ anteinterfaces.ControlPanelKeeper = (*ControlPanelKeeper)(nil)
// ControlPanelKeeper provides control panel functionality for sponsored transactions.
// This is a simple implementation that can be extended to support sponsored addresses
// and custom transaction priorities in the future.
type ControlPanelKeeper struct {
// sponsoredAddresses could be loaded from state or configuration
sponsoredAddresses map[string]bool
// priority for sponsored transactions
sponsoredTxPriority int64
}
// NewControlPanelKeeper creates a new ControlPanelKeeper instance
func NewControlPanelKeeper() *ControlPanelKeeper {
return &ControlPanelKeeper{
sponsoredAddresses: make(map[string]bool),
sponsoredTxPriority: 0, // Default priority
}
}
// IsSponsoredAddress checks if an address is sponsored for gasless transactions
func (k *ControlPanelKeeper) IsSponsoredAddress(ctx context.Context, addr []byte) bool {
// For now, return false for all addresses
// This can be extended to check against a whitelist or state
return false
}
// GetSponsoredTransactionPriority returns the priority for sponsored transactions
func (k *ControlPanelKeeper) GetSponsoredTransactionPriority(ctx context.Context) int64 {
// Return default priority
// This can be made configurable or dynamic based on chain state
return k.sponsoredTxPriority
}
// SetSponsoredAddress adds or removes an address from the sponsored list
// This is a helper method for future use
func (k *ControlPanelKeeper) SetSponsoredAddress(addr sdk.AccAddress, sponsored bool) {
if sponsored {
k.sponsoredAddresses[addr.String()] = true
} else {
delete(k.sponsoredAddresses, addr.String())
}
}
// SetSponsoredTransactionPriority updates the priority for sponsored transactions
// This is a helper method for future use
func (k *ControlPanelKeeper) SetSponsoredTransactionPriority(priority int64) {
k.sponsoredTxPriority = priority
}
+129
View File
@@ -0,0 +1,129 @@
package ante
import (
"context"
addresscodec "cosmossdk.io/core/address"
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
circuitkeeper "cosmossdk.io/x/circuit/keeper"
txsigning "cosmossdk.io/x/tx/signing"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
anteinterfaces "github.com/cosmos/evm/ante/interfaces"
ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper"
)
// WebAuthnKeeperInterface defines the required methods from the DID keeper for WebAuthn gasless processing
type WebAuthnKeeperInterface interface {
// HasExistingCredential checks if this credential ID already exists
HasExistingCredential(ctx sdk.Context, credentialId string) bool
}
// BankKeeper defines the contract needed for supply related APIs.
// It provides methods for checking send permissions and transferring coins
// between accounts and modules.
type BankKeeper interface {
IsSendEnabledCoins(ctx context.Context, coins ...sdk.Coin) error
SendCoins(ctx context.Context, from, to sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromAccountToModule(
ctx context.Context,
senderAddr sdk.AccAddress,
recipientModule string,
amt sdk.Coins,
) error
}
// AccountKeeper defines the account management interface required by ante handlers.
// It provides methods for account creation, retrieval, modification, and
// sequence number management.
type AccountKeeper interface {
NewAccountWithAddress(ctx context.Context, addr sdk.AccAddress) sdk.AccountI
GetModuleAddress(moduleName string) sdk.AccAddress
GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI
SetAccount(ctx context.Context, account sdk.AccountI)
RemoveAccount(ctx context.Context, account sdk.AccountI)
GetParams(ctx context.Context) (params authtypes.Params)
GetSequence(ctx context.Context, addr sdk.AccAddress) (uint64, error)
AddressCodec() addresscodec.Codec
}
// HandlerOptions defines the list of module keepers and configurations required
// to run the ante handler decorators. It includes both standard Cosmos SDK
// keepers and EVM-specific components for processing different transaction types.
type HandlerOptions struct {
Cdc codec.BinaryCodec
AccountKeeper AccountKeeper
BankKeeper BankKeeper
FeegrantKeeper ante.FeegrantKeeper
ExtensionOptionChecker ante.ExtensionOptionChecker
SignModeHandler *txsigning.HandlerMap
SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error
TxFeeChecker ante.TxFeeChecker // safe to be nil
MaxTxGasWanted uint64
FeeMarketKeeper anteinterfaces.FeeMarketKeeper
EvmKeeper anteinterfaces.EVMKeeper
ControlPanelKeeper anteinterfaces.ControlPanelKeeper
IBCKeeper *ibckeeper.Keeper
CircuitKeeper *circuitkeeper.Keeper
// WebAuthn gasless transaction support
DidKeeper WebAuthnKeeperInterface
EnableEnhancedGasless bool // Enable enhanced gasless mode for true onboarding without pre-existing accounts
// UCAN module keepers for permission validation
DwnKeeper interface{} // Will be cast to proper type in decorator
DexKeeper interface{} // Will be cast to proper type in decorator
SvcKeeper interface{} // Will be cast to proper type in decorator
}
// Validate checks if all required keepers and handlers are properly initialized.
// It ensures that the HandlerOptions struct has all necessary components to
// process transactions without nil pointer errors.
func (options HandlerOptions) Validate() error {
if options.Cdc == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "codec is required for AnteHandler")
}
if options.AccountKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "account keeper is required for AnteHandler")
}
if options.BankKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "bank keeper is required for AnteHandler")
}
if options.SigGasConsumer == nil {
return errorsmod.Wrap(
errortypes.ErrLogic,
"signature gas consumer is required for AnteHandler",
)
}
if options.SignModeHandler == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "sign mode handler is required for AnteHandler")
}
if options.CircuitKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "circuit keeper is required for ante builder")
}
if options.TxFeeChecker == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "tx fee checker is required for AnteHandler")
}
if options.FeeMarketKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "fee market keeper is required for AnteHandler")
}
if options.EvmKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "evm keeper is required for AnteHandler")
}
if options.ControlPanelKeeper == nil {
return errorsmod.Wrap(
errortypes.ErrLogic,
"control panel keeper is required for AnteHandler",
)
}
return nil
}
+127
View File
@@ -0,0 +1,127 @@
package ante
import (
"context"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sonr-io/sonr/crypto/keys"
"github.com/sonr-io/sonr/crypto/ucan"
)
// UCANDecorator validates UCAN tokens in transactions
// This is a placeholder implementation that sets up the infrastructure
// for UCAN validation. In production, UCAN tokens would be passed
// in message fields or transaction extensions.
type UCANDecorator struct {
verifier *ucan.Verifier
}
// NewUCANDecorator creates a new UCAN decorator
func NewUCANDecorator() UCANDecorator {
// Create a basic DID resolver
didResolver := &BasicDIDResolver{}
verifier := ucan.NewVerifier(didResolver)
return UCANDecorator{
verifier: verifier,
}
}
// AnteHandle validates UCAN tokens for transactions requiring authorization
func (ud UCANDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// Skip validation in simulation mode
if simulate {
return next(ctx, tx, simulate)
}
// Check if transaction has UCAN extension
// This is where we would extract and validate UCAN tokens
// For now, this is a placeholder that demonstrates the structure
// Future implementation would:
// 1. Extract UCAN token from transaction extensions or memo
// 2. Validate the token using the verifier
// 3. Check capabilities against message types
// 4. Mark transaction as gasless if appropriate
// Check if transaction qualifies for gasless execution
if ud.isGaslessTransaction(ctx, tx) {
// Mark context for gasless processing
ctx = ctx.WithValue("gasless_ucan", true)
}
return next(ctx, tx, simulate)
}
// isGaslessTransaction checks if transaction qualifies for gasless execution
// This is a placeholder implementation
func (ud UCANDecorator) isGaslessTransaction(ctx sdk.Context, tx sdk.Tx) bool {
// In production, this would check for UCAN tokens with gasless capabilities
// For now, return false to maintain normal fee processing
return false
}
// CheckTokenExpiration checks if UCAN token has expired
func (ud UCANDecorator) CheckTokenExpiration(ctx sdk.Context, token *ucan.Token) error {
if token.ExpiresAt > 0 {
currentTime := ctx.BlockTime().Unix()
if currentTime > token.ExpiresAt {
return fmt.Errorf("UCAN token has expired")
}
}
// Check NotBefore
if token.NotBefore > 0 {
currentTime := ctx.BlockTime().Unix()
if currentTime < token.NotBefore {
return fmt.Errorf("UCAN token is not yet valid")
}
}
return nil
}
// ValidateCapabilities validates UCAN capabilities against required permissions
func (ud UCANDecorator) ValidateCapabilities(token *ucan.Token, requiredCapabilities []string) error {
// Check if token grants required capabilities
for _, att := range token.Attenuations {
if att.Capability.Grants(requiredCapabilities) {
return nil
}
}
return fmt.Errorf("UCAN token does not grant required capabilities")
}
// BasicDIDResolver implements ucan.DIDResolver for the ante handler
type BasicDIDResolver struct{}
// ResolveDIDKey resolves DID to public key for UCAN verification
func (r *BasicDIDResolver) ResolveDIDKey(ctx context.Context, did string) (keys.DID, error) {
// This is a basic implementation that accepts all DIDs
// In production, this would query the DID module
return keys.Parse(did)
}
// ConditionalUCANDecorator wraps UCAN decorator to skip for certain transactions
type ConditionalUCANDecorator struct {
decorator sdk.AnteDecorator
}
// NewConditionalUCANDecorator creates a conditional UCAN decorator
func NewConditionalUCANDecorator(decorator sdk.AnteDecorator) ConditionalUCANDecorator {
return ConditionalUCANDecorator{decorator: decorator}
}
// AnteHandle conditionally applies UCAN validation
func (cud ConditionalUCANDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// Skip UCAN validation if already marked as gasless WebAuthn
if ctx.Value("bypass_ucan") != nil {
return next(ctx, tx, simulate)
}
// Apply UCAN validation
return cud.decorator.AnteHandle(ctx, tx, simulate, next)
}
+124
View File
@@ -0,0 +1,124 @@
package ante
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sonr-io/sonr/crypto/ucan"
)
// TestUCANDecorator tests the UCAN decorator functionality
func TestUCANDecorator(t *testing.T) {
// Test decorator creation
decorator := NewUCANDecorator()
assert.NotNil(t, decorator)
}
// TestUCANGaslessDecorator tests the gasless decorator
func TestUCANGaslessDecorator(t *testing.T) {
// Create mock fee decorator
mockFeeDecorator := &mockAnteDecorator{}
// Test gasless decorator creation
gaslessDecorator := NewUCANGaslessDecorator(mockFeeDecorator)
assert.NotNil(t, gaslessDecorator)
}
// TestConditionalUCANDecorator tests conditional UCAN decorator
func TestConditionalUCANDecorator(t *testing.T) {
// Create mock UCAN decorator
mockUCANDecorator := &mockAnteDecorator{}
// Test conditional decorator creation
conditionalDecorator := NewConditionalUCANDecorator(mockUCANDecorator)
assert.NotNil(t, conditionalDecorator)
}
// TestTokenExpiration tests UCAN token expiration validation
func TestTokenExpiration(t *testing.T) {
decorator := NewUCANDecorator()
ctx := sdk.Context{}.WithBlockTime(time.Now())
// Test expired token
expiredToken := &ucan.Token{
ExpiresAt: time.Now().Unix() - 3600, // 1 hour ago
}
err := decorator.CheckTokenExpiration(ctx, expiredToken)
require.Error(t, err)
assert.Contains(t, err.Error(), "UCAN token has expired")
// Test valid token
validToken := &ucan.Token{
ExpiresAt: time.Now().Unix() + 3600, // 1 hour from now
}
err = decorator.CheckTokenExpiration(ctx, validToken)
require.NoError(t, err)
// Test token not yet valid
futureToken := &ucan.Token{
NotBefore: time.Now().Unix() + 3600, // 1 hour from now
}
err = decorator.CheckTokenExpiration(ctx, futureToken)
require.Error(t, err)
assert.Contains(t, err.Error(), "UCAN token is not yet valid")
}
// TestValidateCapabilities tests capability validation
func TestValidateCapabilities(t *testing.T) {
decorator := NewUCANDecorator()
// Create token with single capability
token := &ucan.Token{
Attenuations: []ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{
Action: "did/update",
},
},
},
}
// Test with matching capability
err := decorator.ValidateCapabilities(token, []string{"did/update"})
require.NoError(t, err)
// Test with non-matching capability
err = decorator.ValidateCapabilities(token, []string{"did/delete"})
require.Error(t, err)
assert.Contains(t, err.Error(), "UCAN token does not grant required capabilities")
// Test with multiple capabilities
multiToken := &ucan.Token{
Attenuations: []ucan.Attenuation{
{
Capability: &ucan.MultiCapability{
Actions: []string{"did/update", "did/create"},
},
},
},
}
err = decorator.ValidateCapabilities(multiToken, []string{"did/update"})
require.NoError(t, err)
err = decorator.ValidateCapabilities(multiToken, []string{"did/create"})
require.NoError(t, err)
err = decorator.ValidateCapabilities(multiToken, []string{"did/delete"})
require.Error(t, err)
}
// mockAnteDecorator is a helper for testing
type mockAnteDecorator struct{}
func (m *mockAnteDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
return next(ctx, tx, simulate)
}
+29
View File
@@ -0,0 +1,29 @@
package ante
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// UCANGaslessDecorator allows gasless transactions for UCAN-authorized operations
type UCANGaslessDecorator struct {
feeDecorator sdk.AnteDecorator
}
// NewUCANGaslessDecorator creates a new UCAN gasless decorator
func NewUCANGaslessDecorator(feeDecorator sdk.AnteDecorator) UCANGaslessDecorator {
return UCANGaslessDecorator{
feeDecorator: feeDecorator,
}
}
// AnteHandle conditionally skips fee deduction for UCAN gasless transactions
func (ugd UCANGaslessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// Check if transaction is marked as UCAN gasless
if ctx.Value("gasless_ucan") != nil {
// Skip fee deduction for gasless UCAN transaction
return next(ctx, tx, simulate)
}
// Apply normal fee deduction
return ugd.feeDecorator.AnteHandle(ctx, tx, simulate, next)
}
+81
View File
@@ -0,0 +1,81 @@
package ante
import (
sdk "github.com/cosmos/cosmos-sdk/types"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
didtypes "github.com/sonr-io/sonr/x/did/types"
)
// WebAuthnBypassDecorator completely bypasses signature verification for WebAuthn registration.
// This decorator must be placed FIRST in the ante handler chain to intercept WebAuthn
// transactions before any signature validation occurs.
type WebAuthnBypassDecorator struct{}
// NewWebAuthnBypassDecorator creates a new WebAuthnBypassDecorator
func NewWebAuthnBypassDecorator() WebAuthnBypassDecorator {
return WebAuthnBypassDecorator{}
}
// AnteHandle validates WebAuthn transactions and marks them for controlled processing
// This decorator performs essential security checks while allowing gasless processing
func (wbd WebAuthnBypassDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
msgs := tx.GetMsgs()
// Check if this is a single WebAuthn registration transaction
if len(msgs) != 1 {
// Not a single message transaction, proceed normally
return next(ctx, tx, sim)
}
msg, ok := msgs[0].(*didtypes.MsgRegisterWebAuthnCredential)
if !ok {
// Not a WebAuthn registration, proceed normally
return next(ctx, tx, sim)
}
// This is a WebAuthn registration - perform security validation
ctx.Logger().Info("Processing WebAuthn registration with controlled bypass",
"username", msg.Username,
"credential_id", msg.WebauthnCredential.CredentialId)
// CRITICAL SECURITY CHECK 1: Validate the WebAuthn credential structure
if err := msg.WebauthnCredential.ValidateStructure(); err != nil {
ctx.Logger().Error("WebAuthn credential structure validation failed", "error", err)
return ctx, err
}
// CRITICAL SECURITY CHECK 2: Handle signatures (dummy signatures are allowed for mempool validation)
if sigTx, ok := tx.(authsigning.SigVerifiableTx); ok {
sigs, err := sigTx.GetSignaturesV2()
if err != nil {
ctx.Logger().Error("Failed to get signatures from WebAuthn transaction", "error", err)
return ctx, err
}
if len(sigs) > 0 {
ctx.Logger().
Debug("WebAuthn transaction has dummy signatures for mempool validation", "sig_count", len(sigs))
// This is expected - dummy signatures are used to pass mempool validation
// The actual signature verification will be bypassed by conditional decorators
} else {
ctx.Logger().Debug("WebAuthn transaction has no signatures - gasless flow")
}
}
// CRITICAL SECURITY CHECK 3: Validate credential uniqueness to prevent replay attacks
// Note: This will be enforced in the WebAuthnGaslessDecorator with keeper access
// Mark context for controlled WebAuthn processing
// These flags will be checked by conditional decorators
ctx = ctx.WithValue("webauthn_bypass_validated", true)
ctx.Logger().Info("WebAuthn transaction validated - proceeding with controlled processing",
"credential_id", msg.WebauthnCredential.CredentialId,
"username", msg.Username)
// Continue to next decorator (WebAuthnGaslessDecorator) for full processing
return next(ctx, tx, sim)
}
+359
View File
@@ -0,0 +1,359 @@
// Package ante provides ante handler implementations for transaction processing
// in the Sonr blockchain. It supports both Cosmos SDK and Ethereum transactions.
package ante
import (
"crypto/sha256"
"encoding/hex"
"fmt"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
didtypes "github.com/sonr-io/sonr/x/did/types"
)
// WebAuthnGaslessDecorator provides gasless transaction processing for WebAuthn registration.
// This decorator identifies WebAuthn credential registration messages and bypasses fee deduction,
// enabling users to create their first decentralized identity without requiring existing tokens.
//
// The decorator supports two modes:
// 1. Standard mode: Requires a controller address (for users with existing accounts)
// 2. Enhanced mode: Generates controller address from credential (for brand new users)
//
// Security considerations:
// - Only applies to MsgRegisterWebAuthnCredential messages
// - Validates WebAuthn credential authenticity before fee waiving
// - Prevents abuse through cryptographic WebAuthn requirements
// - Limited to one gasless transaction per unique credential
type WebAuthnGaslessDecorator struct {
accountKeeper AccountKeeper
didKeeper WebAuthnKeeperInterface
enhancedMode bool // If true, allows address generation from credentials
}
// NewWebAuthnGaslessDecorator creates a new WebAuthn gasless transaction decorator.
// This decorator must be placed in the ante handler chain BEFORE the fee deduction decorator
// to effectively bypass fee requirements for qualifying WebAuthn transactions.
//
// Set enhancedMode to true to enable automatic address generation from credentials,
// allowing truly gasless onboarding without pre-existing accounts.
func NewWebAuthnGaslessDecorator(
accountKeeper AccountKeeper,
didKeeper WebAuthnKeeperInterface,
enhancedMode bool,
) WebAuthnGaslessDecorator {
return WebAuthnGaslessDecorator{
accountKeeper: accountKeeper,
didKeeper: didKeeper,
enhancedMode: enhancedMode,
}
}
// AnteHandle processes the transaction and determines if WebAuthn gasless processing applies.
// For qualifying WebAuthn registration transactions, it sets transaction fees to zero
// and validates the WebAuthn credential to prevent abuse.
//
// Gasless criteria:
// 1. Transaction contains exactly one MsgRegisterWebAuthnCredential
// 2. WebAuthn credential passes cryptographic validation
// 3. Credential ID has not been used before (prevents replay attacks)
// 4. Transaction sender account exists or can be created
//
// In enhanced mode, if no controller is provided, it generates one from the credential.
func (wgd WebAuthnGaslessDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
// Process during both CheckTx (simulation) and DeliverTx (execution)
// This ensures gasless flags are set during mempool validation
msgs := tx.GetMsgs()
// Debug: Log entry into gasless decorator
ctx.Logger().Debug("WebAuthnGaslessDecorator: processing transaction",
"msg_count", len(msgs), "sim", sim)
// Check if this is a single WebAuthn registration transaction
if len(msgs) != 1 {
// Multi-message transactions don't qualify for gasless processing
return next(ctx, tx, sim)
}
msg, ok := msgs[0].(*didtypes.MsgRegisterWebAuthnCredential)
if !ok {
// Not a WebAuthn registration message
return next(ctx, tx, sim)
}
// Check if WebAuthn bypass validation already occurred
if bypassed, ok := ctx.Value("webauthn_bypass_validated").(bool); !ok || !bypassed {
// Validate the WebAuthn credential to prevent abuse (if not already validated)
if err := msg.WebauthnCredential.ValidateStructure(); err != nil {
return ctx, errorsmod.Wrapf(
errortypes.ErrInvalidRequest,
"invalid WebAuthn credential for gasless transaction: %v", err,
)
}
}
// Prevent credential reuse (anti-replay protection)
if wgd.didKeeper.HasExistingCredential(ctx, msg.WebauthnCredential.CredentialId) {
return ctx, errorsmod.Wrapf(
errortypes.ErrInvalidRequest,
"WebAuthn credential already registered: %s", msg.WebauthnCredential.CredentialId,
)
}
// Handle controller address
var controllerAddr sdk.AccAddress
// Enhanced mode: Generate address from credential if not provided
if wgd.enhancedMode && msg.GetController() == "" {
// Generate deterministic address from credential ID
controllerAddr = GenerateAddressFromCredential(msg.WebauthnCredential.CredentialId)
// Note: We can't modify the message directly in most cases,
// but we can pass the generated address through context
ctx = ctx.WithValue("generated_controller", controllerAddr.String())
ctx.Logger().Info(
"Generated controller address for gasless WebAuthn registration",
"generated_address", controllerAddr.String(),
"credential_id", msg.WebauthnCredential.CredentialId,
)
} else {
// Standard mode or enhanced mode with provided controller
controllerStr := msg.GetController()
if controllerStr == "" {
return ctx, errorsmod.Wrap(
errortypes.ErrInvalidAddress,
"controller address required for WebAuthn registration",
)
}
controllerAddr, err = sdk.AccAddressFromBech32(controllerStr)
if err != nil {
return ctx, errorsmod.Wrapf(
errortypes.ErrInvalidAddress,
"invalid controller address: %v", err,
)
}
}
// Ensure the account exists (simulation-safe)
account := wgd.accountKeeper.GetAccount(ctx, controllerAddr)
if account == nil {
// Create account if it doesn't exist (common for first-time WebAuthn users)
// Only actually create during execution, not simulation
if !sim {
account = wgd.accountKeeper.NewAccountWithAddress(ctx, controllerAddr)
wgd.accountKeeper.SetAccount(ctx, account)
ctx.Logger().Info(
"Created new account for gasless WebAuthn registration",
"address", controllerAddr.String(),
)
} else {
// During simulation (CheckTx), just create a temporary account for validation
// We don't assign it back to account variable since it's only for validation
wgd.accountKeeper.NewAccountWithAddress(ctx, controllerAddr)
}
}
// Mark this transaction as gasless
gaslessCtx := ctx.WithValue("webauthn_gasless", true)
// In enhanced mode, also mark to skip signature verification
if wgd.enhancedMode {
gaslessCtx = gaslessCtx.WithValue("skip_sig_verification", true)
gaslessCtx = gaslessCtx.WithValue("skip_pubkey_verification", true)
}
// Log the gasless transaction for monitoring and security purposes
ctx.Logger().Info(
"Processing gasless WebAuthn registration",
"controller", controllerAddr.String(),
"credential_id", msg.WebauthnCredential.CredentialId,
"username", msg.Username,
"auto_vault", msg.AutoCreateVault,
"enhanced_mode", wgd.enhancedMode,
)
return next(gaslessCtx, tx, sim)
}
// GenerateAddressFromCredential generates a deterministic address from a WebAuthn credential ID.
// This ensures the same credential always generates the same address, allowing for
// predictable account creation without requiring pre-existing blockchain state.
func GenerateAddressFromCredential(credentialID string) sdk.AccAddress {
// Create a deterministic hash from the credential ID
// Add a domain separator to prevent collisions with other address generation methods
domainSeparator := "webauthn_gasless_v1"
data := domainSeparator + credentialID
// Generate SHA256 hash
hash := sha256.Sum256([]byte(data))
// Take the first 20 bytes for the address (Ethereum-compatible)
return sdk.AccAddress(hash[:20])
}
// GenerateDIDFromCredential generates a deterministic DID from a WebAuthn credential.
// This creates a unique, reproducible DID for each WebAuthn credential.
func GenerateDIDFromCredential(credentialID string, username string) string {
// Create a deterministic hash from credential ID and username
data := credentialID + ":" + username
hash := sha256.Sum256([]byte(data))
// Create a DID with the sonr method
// Format: did:sonr:<hex-encoded-hash-prefix>
didSuffix := hex.EncodeToString(hash[:16]) // Use first 16 bytes for shorter DIDs
return fmt.Sprintf("did:sonr:%s", didSuffix)
}
// ConditionalFeeDecorator wraps the standard fee deduction decorator to conditionally
// skip fee deduction for gasless WebAuthn transactions marked by WebAuthnGaslessDecorator.
// This is the simplest possible implementation that reuses all existing SDK infrastructure.
type ConditionalFeeDecorator struct {
standardFeeDecorator sdk.AnteDecorator
}
// NewConditionalFeeDecorator creates a decorator that conditionally skips fee deduction
// for gasless transactions while maintaining all standard fee logic for other transactions.
func NewConditionalFeeDecorator(standardFeeDecorator sdk.AnteDecorator) ConditionalFeeDecorator {
return ConditionalFeeDecorator{
standardFeeDecorator: standardFeeDecorator,
}
}
// AnteHandle processes transactions and conditionally skips fee deduction for gasless WebAuthn transactions.
func (cfd ConditionalFeeDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
// Check if this transaction was marked as gasless by WebAuthnGaslessDecorator
if gasless, ok := ctx.Value("webauthn_gasless").(bool); ok && gasless {
// Skip fee deduction for gasless WebAuthn transactions
ctx.Logger().Info("Waiving fees for gasless WebAuthn registration")
return next(ctx, tx, sim)
}
// For all other transactions, use the standard fee deduction decorator
return cfd.standardFeeDecorator.AnteHandle(ctx, tx, sim, next)
}
// ConditionalSignatureDecorator wraps signature verification to skip it for gasless transactions
// This is used in enhanced mode where WebAuthn itself is the authentication mechanism.
type ConditionalSignatureDecorator struct {
sigVerifyDecorator sdk.AnteDecorator
}
// NewConditionalSignatureDecorator creates a decorator that conditionally skips signature verification
func NewConditionalSignatureDecorator(
sigVerifyDecorator sdk.AnteDecorator,
) ConditionalSignatureDecorator {
return ConditionalSignatureDecorator{
sigVerifyDecorator: sigVerifyDecorator,
}
}
// AnteHandle conditionally skips signature verification for gasless transactions
func (csd ConditionalSignatureDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
// Check if signature verification should be skipped
if skip, ok := ctx.Value("skip_sig_verification").(bool); ok && skip {
ctx.Logger().Debug("Skipping signature verification for gasless transaction")
return next(ctx, tx, sim)
}
// For all other transactions, use the standard signature verification
return csd.sigVerifyDecorator.AnteHandle(ctx, tx, sim, next)
}
// ConditionalPubKeyDecorator wraps pubkey setting to skip it for gasless transactions
// This is used in enhanced mode where accounts are created without pre-existing keys.
type ConditionalPubKeyDecorator struct {
setPubKeyDecorator sdk.AnteDecorator
}
// NewConditionalPubKeyDecorator creates a decorator that conditionally skips pubkey setting
func NewConditionalPubKeyDecorator(
setPubKeyDecorator sdk.AnteDecorator,
) ConditionalPubKeyDecorator {
return ConditionalPubKeyDecorator{
setPubKeyDecorator: setPubKeyDecorator,
}
}
// AnteHandle conditionally skips pubkey setting for gasless transactions
func (cpd ConditionalPubKeyDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
// Check if pubkey verification should be skipped
if skip, ok := ctx.Value("skip_pubkey_verification").(bool); ok && skip {
ctx.Logger().Debug("Skipping pubkey setting for gasless transaction")
return next(ctx, tx, sim)
}
// For all other transactions, use the standard pubkey decorator
return cpd.setPubKeyDecorator.AnteHandle(ctx, tx, sim, next)
}
// ConditionalSigCountDecorator wraps signature count validation to skip it for gasless transactions
// This is critical for gasless WebAuthn transactions which have no signatures to validate.
type ConditionalSigCountDecorator struct {
sigCountDecorator sdk.AnteDecorator
}
// NewConditionalSigCountDecorator creates a decorator that conditionally skips signature count validation
func NewConditionalSigCountDecorator(
sigCountDecorator sdk.AnteDecorator,
) ConditionalSigCountDecorator {
return ConditionalSigCountDecorator{
sigCountDecorator: sigCountDecorator,
}
}
// AnteHandle conditionally skips signature count validation for gasless transactions
func (cscd ConditionalSigCountDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
// Check if signature verification should be skipped (implies no signatures to count)
if skip, ok := ctx.Value("skip_sig_verification").(bool); ok && skip {
ctx.Logger().Debug("Skipping signature count validation for gasless transaction")
return next(ctx, tx, sim)
}
// For all other transactions, use the standard signature count validator
return cscd.sigCountDecorator.AnteHandle(ctx, tx, sim, next)
}
// ConditionalSigGasDecorator wraps signature gas consumption to skip it for gasless transactions
// This prevents "no signatures supplied" errors for gasless WebAuthn transactions.
type ConditionalSigGasDecorator struct {
sigGasDecorator sdk.AnteDecorator
}
// NewConditionalSigGasDecorator creates a decorator that conditionally skips signature gas consumption
func NewConditionalSigGasDecorator(
sigGasDecorator sdk.AnteDecorator,
) ConditionalSigGasDecorator {
return ConditionalSigGasDecorator{
sigGasDecorator: sigGasDecorator,
}
}
// AnteHandle conditionally skips signature gas consumption for gasless transactions
func (csgd ConditionalSigGasDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
// Check if signature verification should be skipped (implies no signature gas consumption needed)
if skip, ok := ctx.Value("skip_sig_verification").(bool); ok && skip {
ctx.Logger().Debug("Skipping signature gas consumption for gasless transaction")
return next(ctx, tx, sim)
}
// For all other transactions, use the standard signature gas decorator
return csgd.sigGasDecorator.AnteHandle(ctx, tx, sim, next)
}
+271
View File
@@ -0,0 +1,271 @@
package ante_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sonr-io/sonr/app/ante"
)
// MockWebAuthnKeeper implements the WebAuthnKeeperInterface for testing
type MockWebAuthnKeeper struct {
existingCredentials map[string]bool
}
func NewMockWebAuthnKeeper() *MockWebAuthnKeeper {
return &MockWebAuthnKeeper{
existingCredentials: make(map[string]bool),
}
}
func (m *MockWebAuthnKeeper) HasExistingCredential(ctx sdk.Context, credentialId string) bool {
return m.existingCredentials[credentialId]
}
func (m *MockWebAuthnKeeper) AddCredential(credentialId string) {
m.existingCredentials[credentialId] = true
}
// MockAccountKeeper - Simple mock without complex interface implementation
// For comprehensive testing, we'd implement the full AccountKeeper interface
// MockTransaction implements sdk.Tx for testing
type MockTransaction struct {
messages []sdk.Msg
}
func (m *MockTransaction) GetMsgs() []sdk.Msg {
return m.messages
}
func (m *MockTransaction) ValidateBasic() error {
return nil
}
// WebAuthnGaslessTestSuite tests the WebAuthn gasless decorator
type WebAuthnGaslessTestSuite struct {
suite.Suite
didKeeper *MockWebAuthnKeeper
}
func TestWebAuthnGaslessTestSuite(t *testing.T) {
suite.Run(t, new(WebAuthnGaslessTestSuite))
}
func (suite *WebAuthnGaslessTestSuite) SetupTest() {
// Setup minimal test context and keepers
suite.didKeeper = NewMockWebAuthnKeeper()
// We'll focus on testing the logic without complex keeper setup
}
func (suite *WebAuthnGaslessTestSuite) TestGenerateAddressFromCredential() {
// Test that the same credential always generates the same address
credentialID := "test-credential-id-123"
addr1 := ante.GenerateAddressFromCredential(credentialID)
addr2 := ante.GenerateAddressFromCredential(credentialID)
suite.Require().Equal(addr1, addr2, "Same credential should generate same address")
suite.Require().NotNil(addr1, "Generated address should not be nil")
suite.Require().Equal(20, len(addr1), "Address should be 20 bytes")
}
func (suite *WebAuthnGaslessTestSuite) TestGenerateDIDFromCredential() {
// Test DID generation
credentialID := "test-credential-id-123"
username := "testuser"
did1 := ante.GenerateDIDFromCredential(credentialID, username)
did2 := ante.GenerateDIDFromCredential(credentialID, username)
suite.Require().Equal(did1, did2, "Same inputs should generate same DID")
suite.Require().Contains(did1, "did:sonr:", "DID should have correct prefix")
// Different username should generate different DID
did3 := ante.GenerateDIDFromCredential(credentialID, "differentuser")
suite.Require().NotEqual(did1, did3, "Different username should generate different DID")
}
func (suite *WebAuthnGaslessTestSuite) TestWebAuthnGaslessDecorator_StandardMode() {
// Test standard mode behavior
decorator := ante.NewWebAuthnGaslessDecorator(
nil, // accountKeeper would be mocked in full test
suite.didKeeper,
false, // standard mode
)
suite.Require().NotNil(decorator, "Decorator should be created")
}
func (suite *WebAuthnGaslessTestSuite) TestWebAuthnGaslessDecorator_EnhancedMode() {
// Test enhanced mode behavior
decorator := ante.NewWebAuthnGaslessDecorator(
nil, // accountKeeper would be mocked in full test
suite.didKeeper,
true, // enhanced mode
)
suite.Require().NotNil(decorator, "Decorator should be created in enhanced mode")
}
func (suite *WebAuthnGaslessTestSuite) TestConditionalDecorators() {
// Test that conditional decorators are created properly
// We'll use a simple mock decorator for testing
mockDecorator := &MockAnteDecorator{}
feeDecorator := ante.NewConditionalFeeDecorator(mockDecorator)
suite.Require().NotNil(feeDecorator, "Conditional fee decorator should be created")
sigDecorator := ante.NewConditionalSignatureDecorator(mockDecorator)
suite.Require().NotNil(sigDecorator, "Conditional signature decorator should be created")
pubKeyDecorator := ante.NewConditionalPubKeyDecorator(mockDecorator)
suite.Require().NotNil(pubKeyDecorator, "Conditional pubkey decorator should be created")
}
func (suite *WebAuthnGaslessTestSuite) TestWebAuthnCredentialValidation() {
// Test WebAuthn credential structure validation
// This tests the core logic without full ante handler setup
testCases := []struct {
name string
credentialID string
expectError bool
}{
{
name: "valid credential ID",
credentialID: "valid-credential-123",
expectError: false,
},
{
name: "empty credential ID should be caught by message validation",
credentialID: "",
expectError: true, // This would be caught by ValidateStructure
},
{
name: "duplicate credential",
credentialID: "duplicate-cred",
expectError: true,
},
}
// Add a duplicate credential
suite.didKeeper.AddCredential("duplicate-cred")
for _, tc := range testCases {
suite.Run(tc.name, func() {
hasExisting := suite.didKeeper.HasExistingCredential(sdk.Context{}, tc.credentialID)
switch tc.credentialID {
case "duplicate-cred":
suite.Require().True(hasExisting, "Duplicate credential should exist")
case "valid-credential-123":
suite.Require().False(hasExisting, "New credential should not exist")
}
})
}
}
func (suite *WebAuthnGaslessTestSuite) TestEnhancedModeAddressGeneration() {
// Test enhanced mode functionality for address generation
testCredentialID := "test-enhanced-credential"
// Test that the same credential always generates the same address
addr1 := ante.GenerateAddressFromCredential(testCredentialID)
addr2 := ante.GenerateAddressFromCredential(testCredentialID)
suite.Require().Equal(addr1, addr2, "Same credential should generate same address")
suite.Require().Equal(20, len(addr1), "Address should be 20 bytes")
// Test that different credentials generate different addresses
addr3 := ante.GenerateAddressFromCredential("different-credential")
suite.Require().
NotEqual(addr1, addr3, "Different credentials should generate different addresses")
}
// MockAnteDecorator is a simple mock implementation of sdk.AnteDecorator for testing
type MockAnteDecorator struct{}
func (m *MockAnteDecorator) AnteHandle(
ctx sdk.Context,
tx sdk.Tx,
simulate bool,
next sdk.AnteHandler,
) (sdk.Context, error) {
return next(ctx, tx, simulate)
}
// TestAddressGeneration tests the deterministic address generation
func TestAddressGeneration(t *testing.T) {
testCases := []struct {
name string
credentialID string
expectSame bool
}{
{
name: "same credential generates same address",
credentialID: "credential-1",
expectSame: true,
},
{
name: "different credential generates different address",
credentialID: "credential-2",
expectSame: false,
},
}
baseAddr := ante.GenerateAddressFromCredential("base-credential")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
addr := ante.GenerateAddressFromCredential(tc.credentialID)
require.NotNil(t, addr)
require.Equal(t, 20, len(addr))
if tc.expectSame && tc.credentialID == "base-credential" {
require.Equal(t, baseAddr, addr)
} else if !tc.expectSame {
require.NotEqual(t, baseAddr, addr)
}
})
}
}
// TestDIDGeneration tests the deterministic DID generation
func TestDIDGeneration(t *testing.T) {
testCases := []struct {
name string
credentialID string
username string
expectedPrefix string
}{
{
name: "generates valid DID",
credentialID: "cred-1",
username: "user1",
expectedPrefix: "did:sonr:",
},
{
name: "different username generates different DID",
credentialID: "cred-1",
username: "user2",
expectedPrefix: "did:sonr:",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
did := ante.GenerateDIDFromCredential(tc.credentialID, tc.username)
require.NotEmpty(t, did)
require.Contains(t, did, tc.expectedPrefix)
// Verify deterministic generation
did2 := ante.GenerateDIDFromCredential(tc.credentialID, tc.username)
require.Equal(t, did, did2)
})
}
}
+607 -326
View File
File diff suppressed because it is too large Load Diff
Regular → Executable
+29 -2
View File
@@ -5,12 +5,14 @@ import (
abci "github.com/cometbft/cometbft/abci/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/gogoproto/proto"
"github.com/stretchr/testify/require"
"cosmossdk.io/log"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)
func TestAppExport(t *testing.T) {
@@ -34,6 +36,7 @@ func TestAppExport(t *testing.T) {
// Making a new app object with the db, so that initchain hasn't been called
newGapp := NewChainApp(
logger, db, nil, true, simtestutil.NewAppOptionsWithFlagHome(t.TempDir()),
EVMAppOptions,
)
_, err = newGapp.ExportAppStateAndValidators(false, []string{}, nil)
require.NoError(t, err, "ExportAppStateAndValidators should not have an error")
@@ -51,12 +54,36 @@ func TestBlockedAddrs(t *testing.T) {
} else {
addr = gapp.AccountKeeper.GetModuleAddress(acc)
}
require.True(t, gapp.BankKeeper.BlockedAddr(addr), "ensure that blocked addresses are properly set in bank keeper")
require.True(
t,
gapp.BankKeeper.BlockedAddr(addr),
"ensure that blocked addresses are properly set in bank keeper",
)
})
}
}
func TestGetMaccPerms(t *testing.T) {
dup := GetMaccPerms()
require.Equal(t, maccPerms, dup, "duplicated module account permissions differed from actual module account permissions")
require.Equal(
t,
maccPerms,
dup,
"duplicated module account permissions differed from actual module account permissions",
)
}
// TestMergedRegistry tests that fetching the gogo/protov2 merged registry
// doesn't fail after loading all file descriptors.
func TestMergedRegistry(t *testing.T) {
r, err := proto.MergedRegistry()
require.NoError(t, err)
require.Greater(t, r.NumFiles(), 0)
}
func TestProtoAnnotations(t *testing.T) {
r, err := proto.MergedRegistry()
require.NoError(t, err)
err = msgservice.ValidateProtoAnnotations(r)
require.NoError(t, err)
}
+141
View File
@@ -0,0 +1,141 @@
// Package commands contains utility functions for the snrd command
package commands
import (
"crypto/rand"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
"github.com/sonr-io/sonr/app"
"github.com/sonr-io/sonr/crypto/vrf"
"github.com/spf13/cobra"
)
func EnhancedInit(chainApp *app.ChainApp) *cobra.Command {
baseCmd := genutilcli.InitCmd(chainApp.BasicModuleManager, app.DefaultNodeHome)
baseCmd.PostRunE = handleInitPostE
return baseCmd
}
// handleInitPostE generates VRF keypair and stores it securely after chain initialization
func handleInitPostE(cmd *cobra.Command, args []string) error {
// Extract chain-id from genesis file for network-aware key generation
chainID, err := getChainIDFromGenesis()
if err != nil {
return fmt.Errorf("failed to get chain-id: %w", err)
}
// Create deterministic entropy source from chainID
entropySource, err := createChainIDEntropySource(chainID)
if err != nil {
return fmt.Errorf("failed to create entropy source: %w", err)
}
// Generate VRF keypair using chainID-derived entropy
privateKey, err := vrf.GenerateKey(entropySource)
if err != nil {
return fmt.Errorf("failed to generate VRF keypair: %w", err)
}
// Validate the generated keypair
if len(privateKey) != vrf.PrivateKeySize {
return fmt.Errorf(
"invalid VRF private key size: expected %d, got %d",
vrf.PrivateKeySize,
len(privateKey),
)
}
// Get public key to validate keypair
_, ok := privateKey.Public()
if !ok {
return fmt.Errorf("failed to derive public key from VRF private key")
}
// Create secure storage path
vrfKeyPath := filepath.Join(app.DefaultNodeHome, "vrf_secret.key")
// Ensure directory exists with proper permissions
if err := os.MkdirAll(app.DefaultNodeHome, 0o750); err != nil {
return fmt.Errorf("failed to create node home directory: %w", err)
}
// Store VRF secret key with restrictive permissions (owner read/write only)
if err := os.WriteFile(vrfKeyPath, privateKey, 0o600); err != nil {
return fmt.Errorf("failed to save VRF secret key: %w", err)
}
fmt.Printf("✓ VRF keypair generated for network: %s\n", chainID)
fmt.Printf("✓ VRF secret key stored securely: %s\n", vrfKeyPath)
return nil
}
// getChainIDFromGenesis extracts chain-id from the genesis file
func getChainIDFromGenesis() (string, error) {
genesisPath := filepath.Join(app.DefaultNodeHome, "config", "genesis.json")
// #nosec G304 - genesisPath is constructed from trusted app.DefaultNodeHome constant
genesisData, err := os.ReadFile(genesisPath)
if err != nil {
return "", fmt.Errorf("failed to read genesis file: %w", err)
}
var genesis genutiltypes.AppGenesis
if err := json.Unmarshal(genesisData, &genesis); err != nil {
return "", fmt.Errorf("failed to parse genesis file: %w", err)
}
if genesis.ChainID == "" {
return "", fmt.Errorf("chain-id not found in genesis file")
}
return genesis.ChainID, nil
}
// entropyReader implements io.Reader to provide deterministic entropy from chainID
type entropyReader struct {
seed []byte
pos int
}
func (e *entropyReader) Read(p []byte) (int, error) {
n := 0
for n < len(p) && e.pos < len(e.seed) {
p[n] = e.seed[e.pos]
n++
e.pos++
}
// If we need more bytes than available in seed, use crypto/rand for additional randomness
if n < len(p) {
remaining, err := rand.Read(p[n:])
if err != nil {
return n, err
}
n += remaining
}
return n, nil
}
// createChainIDEntropySource creates a deterministic entropy source from chainID
// This provides network-specific but still secure randomness for VRF key generation
func createChainIDEntropySource(chainID string) (io.Reader, error) {
if chainID == "" {
return nil, fmt.Errorf("chainID cannot be empty")
}
// Create deterministic seed from chainID using SHA256
hash := sha256.Sum256([]byte(chainID))
return &entropyReader{
seed: hash[:],
pos: 0,
}, nil
}
+202
View File
@@ -0,0 +1,202 @@
package commands
import (
"crypto/sha256"
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)
func GovCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "gov",
Short: "Governance utilities",
Long: `Utilities for governance operations and compliance.`,
}
cmd.AddCommand(DunaAmendmentCmd())
return cmd
}
func DunaAmendmentCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "duna-amendment",
Short: "Generate Wyoming DAO compliance filing content",
Long: `Extract governance identifiers and generate Wyoming DAO LC amendment filing content
for compliance with Wyoming Statute 17-31-106(b).`,
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
// Use the hardcoded ChainID from app.go
chainID := "sonrtest_1-1"
// Allow override from client context if set
if clientCtx.ChainID != "" {
chainID = clientCtx.ChainID
}
saveToFile, _ := cmd.Flags().GetBool("output-files")
fmt.Printf(
"🏛️ Wyoming DAO compliance check for Decentralized Identity DAO LC (diDAO)\n\n",
)
// Get governance address
govAddress, err := getGovernanceAddress(clientCtx)
if err != nil {
return fmt.Errorf("failed to get governance address: %w", err)
}
fmt.Printf("✓ Governance module address: %s\n", govAddress)
// Get genesis hash
genesisHash, err := getGenesisHash(clientCtx.HomeDir)
if err != nil {
fmt.Printf("⚠️ Warning: Could not calculate genesis hash: %v\n", err)
genesisHash = fmt.Sprintf(
"Genesis file location: %s/config/genesis.json",
clientCtx.HomeDir,
)
} else {
fmt.Printf("✓ Genesis hash: %s\n", genesisHash)
}
// Generate and output filing content
filingContent, amendmentText := generateWyomingContent(govAddress, chainID, genesisHash)
if saveToFile {
err = saveWyomingFiles(filingContent, amendmentText)
if err != nil {
return fmt.Errorf("failed to save Wyoming filing content: %w", err)
}
fmt.Printf("✓ Wyoming filing content generated: wyoming_amendment_content.txt\n")
fmt.Printf("✓ Article VI amendment text: article_vi_amendment.txt\n")
} else {
fmt.Printf("%s", "\n"+filingContent+"\n")
}
fmt.Printf("\n✅ Wyoming compliance identifiers ready!\n\n")
fmt.Printf("Key Information:\n")
fmt.Printf("- Governance Address: %s\n", govAddress)
fmt.Printf("- Chain ID: %s\n", chainID)
fmt.Printf("- Genesis Hash: %s\n", genesisHash)
if saveToFile {
fmt.Printf("\nNext Steps:\n")
fmt.Printf("1. Review: wyoming_amendment_content.txt\n")
fmt.Printf("2. File online at: https://wyobiz.wy.gov\n")
fmt.Printf("3. Pay $60 fee and submit\n")
} else {
fmt.Printf("\nUse --output-files flag to save content to files.\n")
}
return nil
},
}
flags.AddQueryFlagsToCmd(cmd)
cmd.Flags().
Bool("output-files", false, "Save filing content to files instead of printing to stdout")
return cmd
}
func getGovernanceAddress(clientCtx client.Context) (string, error) {
queryClient := authtypes.NewQueryClient(clientCtx)
res, err := queryClient.ModuleAccounts(
clientCtx.CmdContext,
&authtypes.QueryModuleAccountsRequest{},
)
if err != nil {
return "", err
}
// Find the governance module account
for _, account := range res.Accounts {
var acc sdk.AccountI
if err := clientCtx.InterfaceRegistry.UnpackAny(account, &acc); err != nil {
continue
}
if modAcc, ok := acc.(sdk.ModuleAccountI); ok {
if modAcc.GetName() == "gov" {
return acc.GetAddress().String(), nil
}
}
}
return "", fmt.Errorf("governance module account not found")
}
func getGenesisHash(homeDir string) (string, error) {
genesisPath := filepath.Join(homeDir, "config", "genesis.json")
file, err := os.Open(genesisPath)
if err != nil {
return "", err
}
defer file.Close()
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}
func generateWyomingContent(govAddress, chainID, genesisHash string) (string, string) {
now := time.Now()
// Generate full filing content
filingContent := fmt.Sprintf(`WYOMING DAO LC AMENDMENT - ARTICLE VI
Entity Name: Decentralized Identity DAO LC
Amendment Type: Articles of Organization Amendment
ARTICLE VI - DAO PUBLIC IDENTIFIER:
The publicly available identifier for smart contracts used to manage, facilitate, and operate the decentralized autonomous organization is: %s (Cosmos SDK governance module address on chain %s). This identifier provides access to all governance functions including proposal submission, voting, parameter changes, and treasury management as required by Wyoming Statute 17-31-106(b).
VERIFICATION:
- Governance Address: %s
- Chain ID: %s
- Verification Command: snrd query auth module-account gov
Generated on: %s`, govAddress, chainID, govAddress, chainID, now.Format("2006-01-02 15:04:05"))
// Generate Article VI amendment text only
amendmentText := fmt.Sprintf(
`The publicly available identifier for smart contracts used to manage, facilitate, and operate the decentralized autonomous organization is: %s (Cosmos SDK governance module address on chain %s). This identifier provides access to all governance functions including proposal submission, voting, parameter changes, and treasury management as required by Wyoming Statute 17-31-106(b).`,
govAddress,
chainID,
)
return filingContent, amendmentText
}
func saveWyomingFiles(filingContent, amendmentText string) error {
// Write full filing content
err := os.WriteFile("wyoming_amendment_content.txt", []byte(filingContent), 0o644)
if err != nil {
return err
}
// Write Article VI amendment text
err = os.WriteFile("article_vi_amendment.txt", []byte(amendmentText), 0o644)
if err != nil {
return err
}
return nil
}
+267
View File
@@ -0,0 +1,267 @@
// Package commands contains utility functions for the snrd command
package commands
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"github.com/sonr-io/sonr/app"
"github.com/sonr-io/sonr/crypto/vrf"
"github.com/spf13/cobra"
)
// VRFKeysCmd returns the VRF key management command
func VRFKeysCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "vrf",
Short: "Manage VRF (Verifiable Random Function) keys",
Long: `Manage VRF keys used for consensus-based encryption in multi-validator networks.
VRF keys are required for:
- Multi-validator encryption key derivation
- Consensus-based key rotation
- Deterministic randomness in distributed systems
VRF keys are automatically generated during 'snrd init', but this command
allows you to inspect or regenerate them if needed.`,
}
cmd.AddCommand(
vrfGenerateCmd(),
vrfShowCmd(),
vrfVerifyCmd(),
)
return cmd
}
// vrfGenerateCmd creates a command to generate new VRF keys
func vrfGenerateCmd() *cobra.Command {
var chainID string
var force bool
cmd := &cobra.Command{
Use: "generate",
Short: "Generate new VRF keypair",
Long: `Generate a new VRF keypair for the node.
The keypair is generated deterministically from the chain-id to ensure
reproducibility. Keys are stored at ~/.sonr/vrf_secret.key with 0600 permissions.
WARNING: Regenerating VRF keys will invalidate any existing consensus-based
encryption keys. Only do this if you understand the implications.`,
Example: ` # Generate VRF keys for a specific chain
snrd keys vrf generate --chain-id sonrtest_1-1
# Force regenerate (overwrite existing keys)
snrd keys vrf generate --chain-id sonrtest_1-1 --force`,
RunE: func(cmd *cobra.Command, args []string) error {
// Get chain-id
if chainID == "" {
// Try to read from genesis file
var err error
chainID, err = getChainIDFromGenesis()
if err != nil {
return fmt.Errorf("chain-id not provided and could not be read from genesis: %w\n"+
"Please provide --chain-id flag", err)
}
}
vrfKeyPath := filepath.Join(app.DefaultNodeHome, "vrf_secret.key")
// Check if keys already exist
if _, err := os.Stat(vrfKeyPath); err == nil && !force {
return fmt.Errorf("VRF keys already exist at %s\n"+
"Use --force flag to overwrite existing keys\n"+
"WARNING: Overwriting keys will invalidate existing consensus encryption", vrfKeyPath)
}
// Backup existing keys if they exist
if _, err := os.Stat(vrfKeyPath); err == nil && force {
backupPath := vrfKeyPath + ".backup"
if err := os.Rename(vrfKeyPath, backupPath); err != nil {
return fmt.Errorf("failed to backup existing VRF keys: %w", err)
}
fmt.Printf("Backed up existing keys to: %s\n", backupPath)
}
// Generate VRF keys
fmt.Printf("Generating VRF keypair for chain-id: %s\n", chainID)
entropySource, err := createChainIDEntropySource(chainID)
if err != nil {
return fmt.Errorf("failed to create entropy source: %w", err)
}
privateKey, err := vrf.GenerateKey(entropySource)
if err != nil {
return fmt.Errorf("failed to generate VRF keypair: %w", err)
}
// Validate keypair
if len(privateKey) != vrf.PrivateKeySize {
return fmt.Errorf("invalid VRF private key size: expected %d, got %d",
vrf.PrivateKeySize, len(privateKey))
}
publicKey, ok := privateKey.Public()
if !ok {
return fmt.Errorf("failed to derive public key from VRF private key")
}
// Ensure directory exists
if err := os.MkdirAll(app.DefaultNodeHome, 0o750); err != nil {
return fmt.Errorf("failed to create node home directory: %w", err)
}
// Store VRF secret key with restrictive permissions
if err := os.WriteFile(vrfKeyPath, privateKey, 0o600); err != nil {
return fmt.Errorf("failed to save VRF secret key: %w", err)
}
fmt.Printf("✓ VRF keypair generated successfully\n")
fmt.Printf("✓ Private key stored at: %s\n", vrfKeyPath)
fmt.Printf("✓ Public key (hex): %s\n", hex.EncodeToString(publicKey))
fmt.Printf("✓ Key size: %d bytes\n", len(privateKey))
fmt.Printf("✓ Permissions: 0600 (owner read/write only)\n")
return nil
},
}
cmd.Flags().StringVar(&chainID, "chain-id", "", "Chain ID for deterministic key generation")
cmd.Flags().BoolVar(&force, "force", false, "Force overwrite existing VRF keys (creates backup)")
return cmd
}
// vrfShowCmd creates a command to display VRF key information
func vrfShowCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "show",
Short: "Show VRF public key information",
Long: `Display information about the node's VRF public key.
This command shows the public key without exposing the private key.
Useful for verifying that VRF keys are properly installed.`,
Example: ` # Show VRF key information
snrd keys vrf show`,
RunE: func(cmd *cobra.Command, args []string) error {
vrfKeyPath := filepath.Join(app.DefaultNodeHome, "vrf_secret.key")
// Read VRF private key
// #nosec G304 - vrfKeyPath is constructed from trusted DefaultNodeHome constant
privateKeyData, err := os.ReadFile(vrfKeyPath)
if err != nil {
return fmt.Errorf("failed to read VRF keys from %s: %w\n"+
"VRF keys may not be initialized. Run 'snrd init' or 'snrd keys vrf generate'",
vrfKeyPath, err)
}
// Validate key size
if len(privateKeyData) != vrf.PrivateKeySize {
return fmt.Errorf("invalid VRF private key size: expected %d, got %d",
vrf.PrivateKeySize, len(privateKeyData))
}
privateKey := vrf.PrivateKey(privateKeyData)
publicKey, ok := privateKey.Public()
if !ok {
return fmt.Errorf("failed to derive public key from VRF private key")
}
// Get file info
fileInfo, err := os.Stat(vrfKeyPath)
if err != nil {
return fmt.Errorf("failed to stat VRF key file: %w", err)
}
fmt.Println("VRF Key Information:")
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
fmt.Printf("Key Path: %s\n", vrfKeyPath)
fmt.Printf("Public Key: %s\n", hex.EncodeToString(publicKey))
fmt.Printf("Key Size: %d bytes\n", len(privateKeyData))
fmt.Printf("Public Key Size: %d bytes\n", len(publicKey))
fmt.Printf("Permissions: %s\n", fileInfo.Mode().Perm())
fmt.Printf("Modified: %s\n", fileInfo.ModTime().Format("2006-01-02 15:04:05"))
// Verify permissions
if fileInfo.Mode().Perm() != 0o600 {
fmt.Println("\n⚠️ WARNING: VRF key file has incorrect permissions!")
fmt.Printf(" Current: %s, Expected: 0600\n", fileInfo.Mode().Perm())
fmt.Println(" Run: chmod 0600 " + vrfKeyPath)
} else {
fmt.Println("\n✓ VRF keys are properly configured")
}
return nil
},
}
return cmd
}
// vrfVerifyCmd creates a command to verify VRF key functionality
func vrfVerifyCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "verify",
Short: "Verify VRF key functionality",
Long: `Verify that VRF keys are properly installed and functional.
This command performs a test VRF computation to ensure the keys work correctly.`,
Example: ` # Verify VRF keys
snrd keys vrf verify`,
RunE: func(cmd *cobra.Command, args []string) error {
vrfKeyPath := filepath.Join(app.DefaultNodeHome, "vrf_secret.key")
// Read VRF private key
// #nosec G304 - vrfKeyPath is constructed from trusted DefaultNodeHome constant
privateKeyData, err := os.ReadFile(vrfKeyPath)
if err != nil {
return fmt.Errorf("failed to read VRF keys: %w", err)
}
if len(privateKeyData) != vrf.PrivateKeySize {
return fmt.Errorf("invalid VRF private key size: expected %d, got %d",
vrf.PrivateKeySize, len(privateKeyData))
}
privateKey := vrf.PrivateKey(privateKeyData)
publicKey, ok := privateKey.Public()
if !ok {
return fmt.Errorf("failed to derive public key")
}
// Test VRF computation with proof
testInput := []byte("test-input-for-verification")
vrfOutput, proof := privateKey.Prove(testInput)
// Verify the output
if !publicKey.Verify(testInput, vrfOutput, proof) {
return fmt.Errorf("VRF verification failed - keys may be corrupted")
}
// Compute output hash
outputHash := sha256.Sum256(vrfOutput)
fmt.Println("VRF Key Verification:")
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
fmt.Printf("✓ VRF keys loaded successfully\n")
fmt.Printf("✓ Public key derived successfully\n")
fmt.Printf("✓ VRF proof generated successfully\n")
fmt.Printf("✓ VRF verification successful\n")
fmt.Printf("\nTest Output (hex): %s\n", hex.EncodeToString(vrfOutput))
fmt.Printf("Proof (hex): %s\n", hex.EncodeToString(proof))
fmt.Printf("Output Hash: %s\n", hex.EncodeToString(outputHash[:]))
fmt.Println("\n✓ VRF keys are fully functional")
return nil
},
}
return cmd
}
Executable
+122
View File
@@ -0,0 +1,122 @@
// Package app provides configuration utilities for the Sonr blockchain application.
package app
import (
"fmt"
"strings"
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
evmtypes "github.com/cosmos/evm/x/vm/types"
)
// EVMOptionsFn defines a function type for setting app options specifically for
// the app. The function should receive the chainID and return an error if
// any.
type EVMOptionsFn func(string) error
// NoOpEVMOptions is a no-op function that can be used when the app does not
// need any specific configuration.
func NoOpEVMOptions(_ string) error {
return nil
}
// sealed tracks whether the EVM configuration has been initialized.
// Once sealed, the configuration cannot be changed.
var sealed = false
// ChainsCoinInfo is a map of the chain id and its corresponding EvmCoinInfo
// that allows initializing the app with different coin info based on the
// chain id
var ChainsCoinInfo = map[string]evmtypes.EvmCoinInfo{
// Default local development chain
ChainID: {
Denom: BaseDenom,
DisplayDenom: DisplayDenom,
Decimals: evmtypes.EighteenDecimals,
},
// Starship testnet configuration
"sonrtestnet_1-1": {
Denom: BaseDenom,
DisplayDenom: DisplayDenom,
Decimals: evmtypes.EighteenDecimals,
},
"sonr-testnet-1": {
Denom: BaseDenom,
DisplayDenom: DisplayDenom,
Decimals: evmtypes.EighteenDecimals,
},
// Additional testnet configurations
"sonr_1-1": {
Denom: BaseDenom,
DisplayDenom: DisplayDenom,
Decimals: evmtypes.EighteenDecimals,
},
}
// EVMAppOptions sets up the global EVM configuration for the chain.
// It configures the base denomination, chain config, and EVM coin info
// based on the provided chain ID. The configuration is sealed after
// first initialization to prevent modifications.
func EVMAppOptions(chainID string) error {
if sealed {
return nil
}
if chainID == "" {
chainID = ChainID
}
// Try to find config by base chain ID (without suffix)
id := strings.Split(chainID, "-")[0]
coinInfo, found := ChainsCoinInfo[id]
if !found {
// Try full chain ID
coinInfo, found = ChainsCoinInfo[chainID]
if !found {
// Use a default configuration for unknown chains with warning
fmt.Printf("Warning: Unknown chain ID %s, using default usnr configuration\n", chainID)
coinInfo = evmtypes.EvmCoinInfo{
Denom: BaseDenom,
DisplayDenom: DisplayDenom,
Decimals: evmtypes.EighteenDecimals,
}
}
}
// set the denom info for the chain
if err := setBaseDenom(coinInfo); err != nil {
return err
}
baseDenom, err := sdk.GetBaseDenom()
if err != nil {
return err
}
ethCfg := evmtypes.DefaultChainConfig(chainID)
err = evmtypes.NewEVMConfigurator().
WithChainConfig(ethCfg).
WithEVMCoinInfo(baseDenom, uint8(coinInfo.Decimals)).
Configure()
if err != nil {
return err
}
sealed = true
return nil
}
// setBaseDenom registers the display denom and base denom and sets the
// base denom for the chain. It ensures proper decimal conversion between
// the base denomination and display denomination.
func setBaseDenom(ci evmtypes.EvmCoinInfo) error {
if err := sdk.RegisterDenom(ci.DisplayDenom, math.LegacyOneDec()); err != nil {
return err
}
// sdk.RegisterDenom will automatically overwrite the base denom when the
// new setBaseDenom() are lower than the current base denom's units.
return sdk.RegisterDenom(ci.Denom, math.LegacyNewDecWithPrec(1, int64(ci.Decimals)))
}
+214
View File
@@ -0,0 +1,214 @@
package context
import (
"context"
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
)
// BroadcastTx broadcasts a transaction to the blockchain using the stored client context
func (sc *SonrContext) BroadcastTx(txBytes []byte) error {
clientCtx, err := sc.GetClientContext()
if err != nil {
return fmt.Errorf("failed to get client context: %w", err)
}
// Create broadcast request
txReq := &txtypes.BroadcastTxRequest{
TxBytes: txBytes,
Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC,
}
// Get the gRPC client connection
grpcConn := clientCtx.GRPCClient
// Create transaction service client
txClient := txtypes.NewServiceClient(grpcConn)
// Broadcast the transaction
res, err := txClient.BroadcastTx(context.Background(), txReq)
if err != nil {
return fmt.Errorf("failed to broadcast transaction: %w", err)
}
// Check if transaction was accepted
if res.TxResponse.Code != 0 {
return fmt.Errorf(
"transaction failed with code %d: %s",
res.TxResponse.Code,
res.TxResponse.RawLog,
)
}
return nil
}
// BroadcastTxWithResponse broadcasts a transaction and returns the response
func (sc *SonrContext) BroadcastTxWithResponse(
txBytes []byte,
) (*txtypes.BroadcastTxResponse, error) {
clientCtx, err := sc.GetClientContext()
if err != nil {
return nil, fmt.Errorf("failed to get client context: %w", err)
}
// Create broadcast request
txReq := &txtypes.BroadcastTxRequest{
TxBytes: txBytes,
Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC,
}
// Get the gRPC client connection
grpcConn := clientCtx.GRPCClient
// Create transaction service client
txClient := txtypes.NewServiceClient(grpcConn)
// Broadcast the transaction
res, err := txClient.BroadcastTx(context.Background(), txReq)
if err != nil {
return nil, fmt.Errorf("failed to broadcast transaction: %w", err)
}
return res, nil
}
// SignAndBroadcastTx signs a transaction and broadcasts it
func (sc *SonrContext) SignAndBroadcastTx(txBuilder client.TxBuilder) error {
clientCtx, err := sc.GetClientContext()
if err != nil {
return fmt.Errorf("failed to get client context: %w", err)
}
// Sign the transaction
txFactory, err := tx.NewFactoryCLI(clientCtx, nil)
if err != nil {
return fmt.Errorf("failed to create tx factory: %w", err)
}
err = tx.Sign(clientCtx.CmdContext, txFactory, clientCtx.GetFromName(), txBuilder, true)
if err != nil {
return fmt.Errorf("failed to sign transaction: %w", err)
}
// Encode the transaction
txBytes, err := clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
if err != nil {
return fmt.Errorf("failed to encode transaction: %w", err)
}
// Broadcast the transaction
return sc.BroadcastTx(txBytes)
}
// CreateUnsignedTx creates an unsigned transaction from messages
func (sc *SonrContext) CreateUnsignedTx(msgs ...sdk.Msg) (client.TxBuilder, error) {
clientCtx, err := sc.GetClientContext()
if err != nil {
return nil, fmt.Errorf("failed to get client context: %w", err)
}
// Create transaction builder
txBuilder := clientCtx.TxConfig.NewTxBuilder()
// Set messages
err = txBuilder.SetMsgs(msgs...)
if err != nil {
return nil, fmt.Errorf("failed to set messages: %w", err)
}
// Set gas limit and fees (these should be estimated or configured)
txBuilder.SetGasLimit(200000) // Default gas limit
// Set fee amount (you may want to make this configurable)
feeAmount := sdk.NewCoins(sdk.NewInt64Coin("usonr", 1000))
txBuilder.SetFeeAmount(feeAmount)
return txBuilder, nil
}
// EstimateGas estimates gas for a transaction
func (sc *SonrContext) EstimateGas(txBuilder client.TxBuilder) (uint64, error) {
clientCtx, err := sc.GetClientContext()
if err != nil {
return 0, fmt.Errorf("failed to get client context: %w", err)
}
// Simulate the transaction to estimate gas
simReq, err := sc.buildSimTx(clientCtx, txBuilder)
if err != nil {
return 0, fmt.Errorf("failed to build simulation request: %w", err)
}
// Get the gRPC client connection
grpcConn := clientCtx.GRPCClient
// Create transaction service client
txClient := txtypes.NewServiceClient(grpcConn)
// Simulate the transaction
simRes, err := txClient.Simulate(context.Background(), simReq)
if err != nil {
return 0, fmt.Errorf("failed to simulate transaction: %w", err)
}
// Return estimated gas with some buffer
return simRes.GasInfo.GasUsed + 10000, nil
}
// buildSimTx builds a simulation request from a transaction builder
func (sc *SonrContext) buildSimTx(
clientCtx client.Context,
txBuilder client.TxBuilder,
) (*txtypes.SimulateRequest, error) {
// Create a copy of the transaction builder for simulation
simBuilder := clientCtx.TxConfig.NewTxBuilder()
err := simBuilder.SetMsgs(txBuilder.GetTx().GetMsgs()...)
if err != nil {
return nil, err
}
// Get account info for signature
fromAddr := clientCtx.GetFromAddress()
if fromAddr.Empty() {
return nil, fmt.Errorf("from address is empty")
}
// Set dummy signature for simulation - we need a public key from the keyring
keyInfo, err := clientCtx.Keyring.Key(clientCtx.GetFromName())
if err != nil {
return nil, fmt.Errorf("failed to get key info: %w", err)
}
pubKey, err := keyInfo.GetPubKey()
if err != nil {
return nil, fmt.Errorf("failed to get public key: %w", err)
}
sigV2 := signing.SignatureV2{
PubKey: pubKey,
Data: &signing.SingleSignatureData{
SignMode: signing.SignMode_SIGN_MODE_DIRECT,
Signature: nil,
},
Sequence: 0,
}
err = simBuilder.SetSignatures(sigV2)
if err != nil {
return nil, err
}
// Encode the simulation transaction
simTxBytes, err := clientCtx.TxConfig.TxEncoder()(simBuilder.GetTx())
if err != nil {
return nil, err
}
return &txtypes.SimulateRequest{
TxBytes: simTxBytes,
}, nil
}
+196
View File
@@ -0,0 +1,196 @@
// Package context provides the Sonr context system for managing node-specific state.
package context
import (
"fmt"
"os"
"path/filepath"
"sync"
"cosmossdk.io/log"
"github.com/cosmos/cosmos-sdk/client"
"github.com/sonr-io/sonr/crypto/vrf"
)
// SonrContext manages node-specific state and configuration
type SonrContext struct {
logger log.Logger
// VRF keypair for the node
vrfPrivateKey vrf.PrivateKey
vrfPublicKey vrf.PublicKey
// Client context for transaction operations
clientCtx client.Context
// Synchronization for thread-safe access
mu sync.RWMutex
// Initialization state
initialized bool
}
// NewSonrContext creates a new SonrContext instance
func NewSonrContext(logger log.Logger) *SonrContext {
if logger == nil {
logger = log.NewNopLogger()
}
return &SonrContext{
logger: logger.With("component", "sonr-context"),
initialized: false,
}
}
// SetClientContext sets the client context for transaction operations (thread-safe)
func (sc *SonrContext) SetClientContext(clientCtx client.Context) {
sc.mu.Lock()
defer sc.mu.Unlock()
sc.clientCtx = clientCtx
}
// GetClientContext returns the client context (thread-safe)
func (sc *SonrContext) GetClientContext() (client.Context, error) {
sc.mu.RLock()
defer sc.mu.RUnlock()
if sc.clientCtx.Codec == nil {
return client.Context{}, fmt.Errorf("client context not initialized")
}
return sc.clientCtx, nil
}
// Initialize loads the VRF keypair from storage
func (sc *SonrContext) Initialize() error {
sc.mu.Lock()
defer sc.mu.Unlock()
if sc.initialized {
return nil
}
// Load VRF private key from storage
// Use hardcoded default path to avoid import cycle
defaultNodeHome := os.ExpandEnv("$HOME/.sonr")
vrfKeyPath := filepath.Join(defaultNodeHome, "vrf_secret.key")
// #nosec G304 - vrfKeyPath is constructed from trusted DefaultNodeHome constant
vrfKeyData, err := os.ReadFile(vrfKeyPath)
if err != nil {
return fmt.Errorf("failed to read VRF secret key from %s: %w\n"+
"VRF keys are required for multi-validator encryption features.\n"+
"To generate VRF keys:\n"+
" 1. For new nodes: Run 'snrd init <moniker>' to initialize with VRF keys\n"+
" 2. For existing nodes: VRF keys should have been generated during init\n"+
" 3. If encryption is not needed, disable it in DWN module params",
vrfKeyPath, err)
}
// Validate key size
if len(vrfKeyData) != vrf.PrivateKeySize {
return fmt.Errorf(
"invalid VRF private key size: expected %d, got %d",
vrf.PrivateKeySize,
len(vrfKeyData),
)
}
sc.vrfPrivateKey = vrf.PrivateKey(vrfKeyData)
// Derive public key
publicKey, ok := sc.vrfPrivateKey.Public()
if !ok {
return fmt.Errorf("failed to derive VRF public key from private key")
}
sc.vrfPublicKey = publicKey
sc.initialized = true
sc.logger.Info("SonrContext initialized successfully",
"vrf_key_path", vrfKeyPath,
"public_key_size", len(sc.vrfPublicKey),
)
return nil
}
// GetVRFPrivateKey returns the VRF private key (thread-safe)
func (sc *SonrContext) GetVRFPrivateKey() (vrf.PrivateKey, error) {
sc.mu.RLock()
defer sc.mu.RUnlock()
if !sc.initialized {
return nil, fmt.Errorf("SonrContext not initialized")
}
return sc.vrfPrivateKey, nil
}
// GetVRFPublicKey returns the VRF public key (thread-safe)
func (sc *SonrContext) GetVRFPublicKey() (vrf.PublicKey, error) {
sc.mu.RLock()
defer sc.mu.RUnlock()
if !sc.initialized {
return nil, fmt.Errorf("SonrContext not initialized")
}
return sc.vrfPublicKey, nil
}
// IsInitialized returns whether the context has been initialized (thread-safe)
func (sc *SonrContext) IsInitialized() bool {
sc.mu.RLock()
defer sc.mu.RUnlock()
return sc.initialized
}
// ComputeVRF generates VRF output for the given input using the loaded private key
func (sc *SonrContext) ComputeVRF(input []byte) ([]byte, error) {
sc.mu.RLock()
defer sc.mu.RUnlock()
if !sc.initialized {
return nil, fmt.Errorf("SonrContext not initialized")
}
if len(input) == 0 {
return nil, fmt.Errorf("VRF input cannot be empty")
}
return sc.vrfPrivateKey.Compute(input), nil
}
// ProveVRF generates VRF output with proof for the given input
func (sc *SonrContext) ProveVRF(input []byte) (vrf []byte, proof []byte, err error) {
sc.mu.RLock()
defer sc.mu.RUnlock()
if !sc.initialized {
return nil, nil, fmt.Errorf("SonrContext not initialized")
}
if len(input) == 0 {
return nil, nil, fmt.Errorf("VRF input cannot be empty")
}
vrf, proof = sc.vrfPrivateKey.Prove(input)
return vrf, proof, nil
}
// Global context instance (initialized by the node)
var globalSonrContext *SonrContext
// SetGlobalSonrContext sets the global SonrContext instance
func SetGlobalSonrContext(ctx *SonrContext) {
globalSonrContext = ctx
}
// GetGlobalSonrContext returns the global SonrContext instance
func GetGlobalSonrContext() *SonrContext {
return globalSonrContext
}
+160
View File
@@ -0,0 +1,160 @@
package context
import (
"os"
"path/filepath"
"testing"
"cosmossdk.io/log"
"github.com/stretchr/testify/require"
"github.com/sonr-io/sonr/crypto/vrf"
)
// TestSonrContextInitialization tests SonrContext initialization with VRF keys
func TestSonrContextInitialization(t *testing.T) {
require := require.New(t)
// Create temporary directory for test
tmpDir := t.TempDir()
// Test initialization without VRF keys
t.Setenv("HOME", tmpDir)
logger := log.NewNopLogger()
ctx := NewSonrContext(logger)
err := ctx.Initialize()
require.Error(err, "Should fail to initialize without VRF keys")
require.False(ctx.IsInitialized(), "Context should not be initialized without VRF keys")
}
// TestSonrContextWithValidKeys tests SonrContext with valid VRF keys
func TestSonrContextWithValidKeys(t *testing.T) {
require := require.New(t)
// Create temporary directory for test
tmpDir := t.TempDir()
t.Setenv("HOME", tmpDir)
// Create .sonr directory
sonrDir := filepath.Join(tmpDir, ".sonr")
err := os.MkdirAll(sonrDir, 0o750)
require.NoError(err)
// Generate VRF keys
privateKey, err := vrf.GenerateKey(nil)
require.NoError(err)
// Write VRF keys
vrfKeyPath := filepath.Join(sonrDir, "vrf_secret.key")
err = os.WriteFile(vrfKeyPath, privateKey, 0o600)
require.NoError(err)
// Test initialization with valid VRF keys
logger := log.NewNopLogger()
ctx := NewSonrContext(logger)
err = ctx.Initialize()
require.NoError(err, "Should initialize successfully with valid VRF keys")
require.True(ctx.IsInitialized(), "Context should be initialized")
// Test VRF key retrieval
privKey, err := ctx.GetVRFPrivateKey()
require.NoError(err)
require.Len(privKey, vrf.PrivateKeySize)
pubKey, err := ctx.GetVRFPublicKey()
require.NoError(err)
require.Len(pubKey, vrf.PublicKeySize)
}
// TestSonrContextInvalidKeySize tests handling of invalid key size
func TestSonrContextInvalidKeySize(t *testing.T) {
require := require.New(t)
// Create temporary directory for test
tmpDir := t.TempDir()
t.Setenv("HOME", tmpDir)
// Create .sonr directory
sonrDir := filepath.Join(tmpDir, ".sonr")
err := os.MkdirAll(sonrDir, 0o750)
require.NoError(err)
// Write invalid VRF key (wrong size)
vrfKeyPath := filepath.Join(sonrDir, "vrf_secret.key")
invalidKey := make([]byte, 32) // Should be 64 bytes
err = os.WriteFile(vrfKeyPath, invalidKey, 0o600)
require.NoError(err)
// Test initialization with invalid key size
logger := log.NewNopLogger()
ctx := NewSonrContext(logger)
err = ctx.Initialize()
require.Error(err, "Should fail to initialize with invalid key size")
require.Contains(err.Error(), "invalid VRF private key size")
require.False(ctx.IsInitialized(), "Context should not be initialized with invalid keys")
}
// TestSonrContextThreadSafety tests thread-safe access to VRF keys
func TestSonrContextThreadSafety(t *testing.T) {
require := require.New(t)
// Create temporary directory for test
tmpDir := t.TempDir()
t.Setenv("HOME", tmpDir)
// Create .sonr directory with valid keys
sonrDir := filepath.Join(tmpDir, ".sonr")
err := os.MkdirAll(sonrDir, 0o750)
require.NoError(err)
privateKey, err := vrf.GenerateKey(nil)
require.NoError(err)
vrfKeyPath := filepath.Join(sonrDir, "vrf_secret.key")
err = os.WriteFile(vrfKeyPath, privateKey, 0o600)
require.NoError(err)
// Initialize context
logger := log.NewNopLogger()
ctx := NewSonrContext(logger)
err = ctx.Initialize()
require.NoError(err)
// Test concurrent access (simple check - not exhaustive)
done := make(chan bool, 10)
for i := 0; i < 10; i++ {
go func() {
_, err := ctx.GetVRFPrivateKey()
require.NoError(err)
_, err = ctx.GetVRFPublicKey()
require.NoError(err)
done <- true
}()
}
// Wait for all goroutines
for i := 0; i < 10; i++ {
<-done
}
}
// TestSonrContextErrorMessages tests that error messages are helpful
func TestSonrContextErrorMessages(t *testing.T) {
require := require.New(t)
// Create temporary directory for test
tmpDir := t.TempDir()
t.Setenv("HOME", tmpDir)
logger := log.NewNopLogger()
ctx := NewSonrContext(logger)
err := ctx.Initialize()
require.Error(err)
require.Contains(err.Error(), "failed to read VRF secret key")
require.Contains(err.Error(), "VRF keys are required")
require.Contains(err.Error(), "snrd init")
}
+17 -3
View File
@@ -1,3 +1,5 @@
// Package decorators provides custom ante handler decorators for transaction processing
// in the Sonr blockchain application.
package decorators
import (
@@ -8,7 +10,8 @@ import (
"github.com/cosmos/gogoproto/proto"
)
// MsgFilterDecorator is an ante.go decorator template for filtering messages.
// MsgFilterDecorator is an ante handler decorator that filters out specific message types
// from transactions. It prevents certain message types from being processed by the chain.
type MsgFilterDecorator struct {
blockedTypes []sdk.Msg
}
@@ -17,7 +20,8 @@ type MsgFilterDecorator struct {
// contains any of the blocked message types.
//
// Example:
// - decorators.FilterDecorator(&banktypes.MsgSend{})
// - decorators.FilterDecorator(&banktypes.MsgSend{})
//
// This would block any MsgSend messages from being included in a transaction if set in ante.go
func FilterDecorator(blockedMsgTypes ...sdk.Msg) MsgFilterDecorator {
return MsgFilterDecorator{
@@ -25,7 +29,14 @@ func FilterDecorator(blockedMsgTypes ...sdk.Msg) MsgFilterDecorator {
}
}
func (mfd MsgFilterDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
// AnteHandle implements the AnteDecorator interface. It checks if the transaction
// contains any disallowed message types and rejects it if found.
func (mfd MsgFilterDecorator) AnteHandle(
ctx sdk.Context,
tx sdk.Tx,
simulate bool,
next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
if mfd.HasDisallowedMessage(ctx, tx.GetMsgs()) {
currHeight := ctx.BlockHeight()
return ctx, fmt.Errorf("tx contains unsupported message types at height %d", currHeight)
@@ -34,6 +45,9 @@ func (mfd MsgFilterDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo
return next(ctx, tx, simulate)
}
// HasDisallowedMessage recursively checks if any of the provided messages or their
// nested messages (in case of authz.MsgExec) match the blocked message types.
// Returns true if a disallowed message is found.
func (mfd MsgFilterDecorator) HasDisallowedMessage(ctx sdk.Context, msgs []sdk.Msg) bool {
for _, msg := range msgs {
// check nested messages in a recursive manner
+4 -10
View File
@@ -10,21 +10,13 @@ import (
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/stretchr/testify/suite"
app "github.com/sonr-io/snrd/app"
"github.com/sonr-io/snrd/app/decorators"
"github.com/sonr-io/sonr/app/decorators"
)
type AnteTestSuite struct {
suite.Suite
ctx sdk.Context
app *app.SonrApp
}
func (s *AnteTestSuite) SetupTest() {
isCheckTx := false
s.app = app.Setup(s.T())
s.ctx = s.app.BaseApp.NewContext(isCheckTx)
}
func TestAnteTestSuite(t *testing.T) {
@@ -48,7 +40,9 @@ func (s *AnteTestSuite) TestAnteMsgFilterLogic() {
// validate other messages go through still (such as MsgMultiSend)
msgMultiSend := banktypes.NewMsgMultiSend(
banktypes.NewInput(acc, sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(1)))),
[]banktypes.Output{banktypes.NewOutput(acc, sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(1))))},
[]banktypes.Output{
banktypes.NewOutput(acc, sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(1)))),
},
)
_, err = ante.AnteHandle(s.ctx, decorators.NewMockTx(msgMultiSend), false, decorators.EmptyAnte)
s.Require().NoError(err)
Regular → Executable
+9 -1
View File
@@ -5,31 +5,39 @@ import (
protov2 "google.golang.org/protobuf/proto"
)
// Define an empty ante handle
// EmptyAnte is a no-op ante handler used for testing purposes.
// It simply returns the context without performing any operations.
var (
EmptyAnte = func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
return ctx, nil
}
)
// MockTx is a mock transaction implementation used for testing decorators.
// It implements the sdk.Tx interface with minimal functionality.
type MockTx struct {
msgs []sdk.Msg
}
// NewMockTx creates a new mock transaction with the provided messages.
// This is useful for testing ante handler decorators in isolation.
func NewMockTx(msgs ...sdk.Msg) MockTx {
return MockTx{
msgs: msgs,
}
}
// GetMsgs returns the messages contained in the mock transaction.
func (tx MockTx) GetMsgs() []sdk.Msg {
return tx.msgs
}
// GetMsgsV2 implements the sdk.Tx interface. Returns nil as this is a mock.
func (tx MockTx) GetMsgsV2() ([]protov2.Message, error) {
return nil, nil
}
// ValidateBasic implements the sdk.Tx interface. Always returns nil for the mock.
func (tx MockTx) ValidateBasic() error {
return nil
}
Regular → Executable
+36 -3
View File
@@ -1,18 +1,26 @@
// Package app provides encoding utilities for the Sonr blockchain application.
package app
import (
"testing"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/gogoproto/proto"
"cosmossdk.io/log"
"cosmossdk.io/x/tx/signing"
"github.com/cosmos/cosmos-sdk/codec/address"
"github.com/cosmos/cosmos-sdk/codec/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sonr-io/snrd/app/params"
"github.com/sonr-io/sonr/app/params"
)
// MakeEncodingConfig creates a new EncodingConfig with all modules registered. For testing only
// MakeEncodingConfig creates a new EncodingConfig with all modules registered.
// This function is intended for testing purposes only, as it creates a temporary
// application instance to extract the encoding configuration.
func MakeEncodingConfig(t testing.TB) params.EncodingConfig {
t.Helper()
// we "pre"-instantiate the application for getting the injected/configured encoding configuration
@@ -23,11 +31,15 @@ func MakeEncodingConfig(t testing.TB) params.EncodingConfig {
nil,
true,
simtestutil.NewAppOptionsWithFlagHome(t.TempDir()),
EVMAppOptions,
)
return makeEncodingConfig(tempApp)
}
func makeEncodingConfig(tempApp *SonrApp) params.EncodingConfig {
// makeEncodingConfig extracts the encoding configuration from a ChainApp instance.
// It returns an EncodingConfig struct containing the interface registry, codec,
// transaction config, and amino codec.
func makeEncodingConfig(tempApp *ChainApp) params.EncodingConfig {
encodingConfig := params.EncodingConfig{
InterfaceRegistry: tempApp.InterfaceRegistry(),
Codec: tempApp.AppCodec(),
@@ -36,3 +48,24 @@ func makeEncodingConfig(tempApp *SonrApp) params.EncodingConfig {
}
return encodingConfig
}
// GetInterfaceRegistry creates and returns a new interface registry with proper
// address codecs configured. This registry is used for protobuf Any type
// registration and message routing.
func GetInterfaceRegistry() types.InterfaceRegistry {
interfaceRegistry, err := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{
ProtoFiles: proto.HybridResolver,
SigningOptions: signing.Options{
AddressCodec: address.Bech32Codec{
Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(),
},
ValidatorAddressCodec: address.Bech32Codec{
Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(),
},
},
})
if err != nil {
panic(err)
}
return interfaceRegistry
}
Regular → Executable
+118 -82
View File
@@ -1,3 +1,4 @@
// Package app provides export functionality for the Sonr blockchain application.
package app
import (
@@ -5,8 +6,10 @@ import (
"fmt"
"log"
storetypes "cosmossdk.io/store/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
storetypes "cosmossdk.io/store/types"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
sdk "github.com/cosmos/cosmos-sdk/types"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
@@ -15,8 +18,19 @@ import (
)
// ExportAppStateAndValidators exports the state of the application for a genesis
// file.
func (app *SonrApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAddrs, modulesToExport []string) (servertypes.ExportedApp, error) {
// file. It can export at the current height or prepare the state for a zero-height
// genesis, which is useful for chain upgrades or migrations.
//
// Parameters:
// - forZeroHeight: If true, prepares the state for zero-height genesis
// - jailAllowedAddrs: List of validator addresses allowed to remain unjailed
// - modulesToExport: Specific modules to export (exports all if empty)
//
// Returns the exported application state and validator set.
func (app *ChainApp) ExportAppStateAndValidators(
forZeroHeight bool,
jailAllowedAddrs, modulesToExport []string,
) (servertypes.ExportedApp, error) {
// as if they could withdraw from the start of the next block
ctx := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
@@ -39,22 +53,39 @@ func (app *SonrApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedA
}
validators, err := staking.WriteValidators(ctx, app.StakingKeeper)
if err != nil {
return servertypes.ExportedApp{}, err
}
return servertypes.ExportedApp{
AppState: appState,
Validators: validators,
Height: height,
ConsensusParams: app.GetConsensusParams(ctx),
}, err
}, nil
}
// prepare for fresh start at zero height
// NOTE zero height genesis is a temporary feature which will be deprecated
// prepForZeroHeightGenesis prepares the application state for a fresh start at zero height.
// This includes withdrawing all rewards, resetting validator states, and optionally
// jailing validators not in the allowed list. This function modifies the state to ensure
// a clean genesis export.
//
// in favor of export at a block height
func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) {
// NOTE: Zero height genesis is a temporary feature which will be deprecated
// in favor of export at a block height.
func (app *ChainApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) {
var err error
// Just to be safe, assert the invariants on current state.
app.CrisisKeeper.AssertInvariants(ctx)
// set context height to zero
height := ctx.BlockHeight()
ctx = ctx.WithBlockHeight(0)
applyAllowedAddrs := len(jailAllowedAddrs) > 0
// check if there is a allowed address list
allowedAddrsMap := make(map[string]bool)
for _, addr := range jailAllowedAddrs {
@@ -65,20 +96,20 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
allowedAddrsMap[addr] = true
}
// Just to be safe, assert the invariants on current state.
app.CrisisKeeper.AssertInvariants(ctx)
// Handle fee distribution state.
// withdraw all validator commission
err := app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator())
if err != nil {
panic(err)
}
_, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, valBz)
return false
})
err = app.StakingKeeper.IterateValidators(
ctx,
func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator())
if err != nil {
panic(err)
}
_, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, valBz)
return false
},
)
if err != nil {
panic(err)
}
@@ -90,9 +121,9 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
}
for _, delegation := range dels {
valAddr, errB := sdk.ValAddressFromBech32(delegation.ValidatorAddress)
if errB != nil {
panic(errB)
valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress)
if err != nil {
panic(err)
}
delAddr := sdk.MustAccAddressFromBech32(delegation.DelegatorAddress)
@@ -108,55 +139,54 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
// clear validator historical rewards
app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx)
// set context height to zero
height := ctx.BlockHeight()
ctx = ctx.WithBlockHeight(0)
// reinitialize all validators
err = app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
valBz, errB := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator())
if errB != nil {
panic(errB)
}
// donate any unwithdrawn outstanding reward fraction tokens to the community pool
scraps, errC := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz)
if errC != nil {
panic(errC)
}
feePool, errD := app.DistrKeeper.FeePool.Get(ctx)
if errD != nil {
panic(errD)
}
feePool.CommunityPool = feePool.CommunityPool.Add(scraps...)
if errE := app.DistrKeeper.FeePool.Set(ctx, feePool); errE != nil {
panic(errE)
}
err = app.StakingKeeper.IterateValidators(
ctx,
func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator())
if err != nil {
panic(err)
}
// donate any unwithdrawn outstanding reward fraction tokens to the community pool
scraps, err := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz)
if err != nil {
panic(err)
}
feePool, err := app.DistrKeeper.FeePool.Get(ctx)
if err != nil {
panic(err)
}
feePool.CommunityPool = feePool.CommunityPool.Add(scraps...)
if err := app.DistrKeeper.FeePool.Set(ctx, feePool); err != nil {
panic(err)
}
if errF := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, valBz); errF != nil {
panic(errF)
}
return false
})
if err := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, valBz); err != nil {
panic(err)
}
return false
},
)
if err != nil {
panic(err)
}
// reinitialize all delegations
for _, del := range dels {
valAddr, errcc := sdk.ValAddressFromBech32(del.ValidatorAddress)
if errcc != nil {
panic(errcc)
valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress)
if err != nil {
panic(err)
}
delAddr := sdk.MustAccAddressFromBech32(del.DelegatorAddress)
if errcb := app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr); errcb != nil {
if err := app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr); err != nil {
// never called as BeforeDelegationCreated always returns nil
panic(fmt.Errorf("error while incrementing period: %w", errcb))
panic(fmt.Errorf("error while incrementing period: %w", err))
}
if errcd := app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr); errcd != nil {
if err := app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr); err != nil {
// never called as AfterDelegationModified always returns nil
panic(fmt.Errorf("error while creating a new delegation period record: %w", errcd))
panic(fmt.Errorf("error while creating a new delegation period record: %w", err))
}
}
@@ -166,31 +196,37 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
// Handle staking state.
// iterate through redelegations, reset creation height
err = app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) {
for i := range red.Entries {
red.Entries[i].CreationHeight = 0
}
err = app.StakingKeeper.SetRedelegation(ctx, red)
if err != nil {
panic(err)
}
return false
})
err = app.StakingKeeper.IterateRedelegations(
ctx,
func(_ int64, red stakingtypes.Redelegation) (stop bool) {
for i := range red.Entries {
red.Entries[i].CreationHeight = 0
}
err = app.StakingKeeper.SetRedelegation(ctx, red)
if err != nil {
panic(err)
}
return false
},
)
if err != nil {
panic(err)
}
// iterate through unbonding delegations, reset creation height
err = app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) {
for i := range ubd.Entries {
ubd.Entries[i].CreationHeight = 0
}
err = app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)
if err != nil {
panic(err)
}
return false
})
err = app.StakingKeeper.IterateUnbondingDelegations(
ctx,
func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) {
for i := range ubd.Entries {
ubd.Entries[i].CreationHeight = 0
}
err = app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)
if err != nil {
panic(err)
}
return false
},
)
if err != nil {
panic(err)
}
@@ -202,8 +238,8 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
for ; iter.Valid(); iter.Next() {
addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key()))
validator, errr := app.StakingKeeper.GetValidator(ctx, addr)
if errr != nil {
validator, err := app.StakingKeeper.GetValidator(ctx, addr)
if err != nil {
panic("expected validator, not found")
}
@@ -218,8 +254,8 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
}
}
if erra := iter.Close(); erra != nil {
app.Logger().Error("error while closing the key-value store reverse prefix iterator: ", erra)
if err := iter.Close(); err != nil {
app.Logger().Error("error while closing the key-value store reverse prefix iterator: ", err)
return
}
@@ -235,8 +271,8 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
ctx,
func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) {
info.StartHeight = 0
if errb := app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info); errb != nil {
panic(errb)
if err := app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info); err != nil {
panic(err)
}
return false
},
Regular → Executable
+8 -7
View File
@@ -1,14 +1,15 @@
// Package app provides genesis state management for the Sonr blockchain application.
package app
import (
"encoding/json"
)
// GenesisState of the blockchain is represented here as a map of raw json
// messages key'd by a identifier string.
// The identifier is used to determine which module genesis information belongs
// to so it may be appropriately routed during init chain.
// Within this application default genesis information is retrieved from
// the ModuleBasicManager which populates json from each BasicModule
// object provided to it during init.
// GenesisState represents the initial state of the blockchain as a map of raw JSON
// messages keyed by module identifier strings. Each module's genesis state is stored
// as raw JSON to allow flexible initialization during chain setup.
//
// The identifier is used to route genesis information to the appropriate module
// during the init chain process. Default genesis information is populated by
// the ModuleBasicManager from each registered BasicModule.
type GenesisState map[string]json.RawMessage
Regular → Executable
+7 -3
View File
@@ -1,11 +1,12 @@
/*
Package params defines the simulation parameters in the gaia.
Package params defines the simulation parameters and encoding configuration
for the Sonr blockchain application.
It contains the default weights used for each transaction used on the module's
simulation. These weights define the chance for a transaction to be simulated at
any gived operation.
any given operation.
You can repace the default values for the weights by providing a params.json
You can replace the default values for the weights by providing a params.json
file with the weights defined for each of the transaction operations:
{
@@ -15,5 +16,8 @@ file with the weights defined for each of the transaction operations:
In the example above, the `MsgSend` has 60% chance to be simulated, while the
`MsgDelegate` will always be simulated.
The package also provides encoding configuration types used throughout the
application for consistent codec usage.
*/
package params
Regular → Executable
+2
View File
@@ -8,6 +8,8 @@ import (
// EncodingConfig specifies the concrete encoding types to use for a given app.
// This is provided for compatibility between protobuf and amino implementations.
// It encapsulates all the required components for encoding/decoding transactions
// and other data structures in the blockchain.
type EncodingConfig struct {
InterfaceRegistry types.InterfaceRegistry
Codec codec.Codec
Regular → Executable
+3 -1
View File
@@ -12,7 +12,9 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth/tx"
)
// MakeEncodingConfig creates an EncodingConfig for an amino based test configuration.
// MakeEncodingConfig creates a default EncodingConfig with standard Cosmos SDK
// encoding settings. It sets up the interface registry with proper address codecs,
// creates the protobuf codec, and configures transaction handling with default sign modes.
func MakeEncodingConfig() EncodingConfig {
amino := codec.NewLegacyAmino()
interfaceRegistry, err := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{
+127
View File
@@ -0,0 +1,127 @@
// Package app provides EVM precompiled contracts configuration for the Sonr blockchain.
package app
import (
"fmt"
"maps"
evidencekeeper "cosmossdk.io/x/evidence/keeper"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"
slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
bankprecompile "github.com/cosmos/evm/precompiles/bank"
"github.com/cosmos/evm/precompiles/bech32"
distprecompile "github.com/cosmos/evm/precompiles/distribution"
evidenceprecompile "github.com/cosmos/evm/precompiles/evidence"
govprecompile "github.com/cosmos/evm/precompiles/gov"
ics20precompile "github.com/cosmos/evm/precompiles/ics20"
"github.com/cosmos/evm/precompiles/p256"
slashingprecompile "github.com/cosmos/evm/precompiles/slashing"
stakingprecompile "github.com/cosmos/evm/precompiles/staking"
erc20Keeper "github.com/cosmos/evm/x/erc20/keeper"
transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper"
"github.com/cosmos/evm/x/vm/core/vm"
evmkeeper "github.com/cosmos/evm/x/vm/keeper"
channelkeeper "github.com/cosmos/ibc-go/v8/modules/core/04-channel/keeper"
"github.com/ethereum/go-ethereum/common"
)
// bech32PrecompileBaseGas defines the base gas cost for bech32 address conversion operations.
const bech32PrecompileBaseGas = 6_000
// NewAvailableStaticPrecompiles returns the list of all available static precompiled contracts from EVM.
// These precompiles provide native Cosmos SDK functionality accessible from EVM contracts,
// including staking, distribution, governance, bank transfers, IBC transfers, and more.
//
// The function initializes both stateless precompiles (bech32, p256) and stateful precompiles
// that interact with Cosmos SDK modules.
//
// NOTE: this should only be used during initialization of the Keeper.
func NewAvailableStaticPrecompiles(
stakingKeeper stakingkeeper.Keeper,
distributionKeeper distributionkeeper.Keeper,
bankKeeper bankkeeper.Keeper,
erc20Keeper erc20Keeper.Keeper,
authzKeeper authzkeeper.Keeper,
transferKeeper transferkeeper.Keeper,
channelKeeper channelkeeper.Keeper,
evmKeeper *evmkeeper.Keeper,
govKeeper govkeeper.Keeper,
slashingKeeper slashingkeeper.Keeper,
evidenceKeeper evidencekeeper.Keeper,
) map[common.Address]vm.PrecompiledContract {
// Clone the mapping from the latest EVM fork.
precompiles := maps.Clone(vm.PrecompiledContractsBerlin)
// secp256r1 precompile as per EIP-7212
p256Precompile := &p256.Precompile{}
bech32Precompile, err := bech32.NewPrecompile(bech32PrecompileBaseGas)
if err != nil {
panic(fmt.Errorf("failed to instantiate bech32 precompile: %w", err))
}
stakingPrecompile, err := stakingprecompile.NewPrecompile(stakingKeeper, authzKeeper)
if err != nil {
panic(fmt.Errorf("failed to instantiate staking precompile: %w", err))
}
distributionPrecompile, err := distprecompile.NewPrecompile(
distributionKeeper,
stakingKeeper,
authzKeeper,
evmKeeper,
)
if err != nil {
panic(fmt.Errorf("failed to instantiate distribution precompile: %w", err))
}
ibcTransferPrecompile, err := ics20precompile.NewPrecompile(
stakingKeeper,
transferKeeper,
channelKeeper,
authzKeeper,
evmKeeper,
)
if err != nil {
panic(fmt.Errorf("failed to instantiate ICS20 precompile: %w", err))
}
bankPrecompile, err := bankprecompile.NewPrecompile(bankKeeper, erc20Keeper)
if err != nil {
panic(fmt.Errorf("failed to instantiate bank precompile: %w", err))
}
govPrecompile, err := govprecompile.NewPrecompile(govKeeper, authzKeeper)
if err != nil {
panic(fmt.Errorf("failed to instantiate gov precompile: %w", err))
}
slashingPrecompile, err := slashingprecompile.NewPrecompile(slashingKeeper, authzKeeper)
if err != nil {
panic(fmt.Errorf("failed to instantiate slashing precompile: %w", err))
}
evidencePrecompile, err := evidenceprecompile.NewPrecompile(evidenceKeeper, authzKeeper)
if err != nil {
panic(fmt.Errorf("failed to instantiate evidence precompile: %w", err))
}
// Stateless precompiles
precompiles[bech32Precompile.Address()] = bech32Precompile
precompiles[p256Precompile.Address()] = p256Precompile
// Stateful precompiles
precompiles[stakingPrecompile.Address()] = stakingPrecompile
precompiles[distributionPrecompile.Address()] = distributionPrecompile
precompiles[ibcTransferPrecompile.Address()] = ibcTransferPrecompile
precompiles[bankPrecompile.Address()] = bankPrecompile
precompiles[govPrecompile.Address()] = govPrecompile
precompiles[slashingPrecompile.Address()] = slashingPrecompile
precompiles[evidencePrecompile.Address()] = evidencePrecompile
return precompiles
}
Regular → Executable
+87 -21
View File
@@ -82,7 +82,10 @@ func TestFullAppSimulation(t *testing.T) {
}
func TestAppImportExport(t *testing.T) {
config, db, appOptions, app := setupSimulationApp(t, "skipping application import/export simulation")
config, db, appOptions, app := setupSimulationApp(
t,
"skipping application import/export simulation",
)
// Run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
@@ -113,7 +116,13 @@ func TestAppImportExport(t *testing.T) {
t.Log("importing genesis...\n")
newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
newDB, newDir, _, _, err := simtestutil.SetupSimulation(
config,
"leveldb-app-sim-2",
"Simulation-2",
simcli.FlagVerboseValue,
simcli.FlagEnabledValue,
)
require.NoError(t, err, "simulation setup failed")
defer func() {
@@ -121,7 +130,9 @@ func TestAppImportExport(t *testing.T) {
require.NoError(t, os.RemoveAll(newDir))
}()
newApp := NewChainApp(log.NewNopLogger(), newDB, nil, true, appOptions, nil, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
newApp := NewChainApp(log.NewNopLogger(), newDB, nil, true, appOptions,
EVMAppOptions,
fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
initReq := &abci.RequestInitChain{
AppStateBytes: exported.AppState,
@@ -130,7 +141,6 @@ func TestAppImportExport(t *testing.T) {
ctxA := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
ctxB := newApp.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
_, err = newApp.InitChainer(ctxB, initReq)
if err != nil {
if strings.Contains(err.Error(), "validator set is empty after InitGenesis") {
t.Log("Skipping simulation as all validators have been unbonded")
@@ -172,17 +182,38 @@ func TestAppImportExport(t *testing.T) {
storeB := ctxB.KVStore(appKeyB)
failedKVAs, failedKVBs := simtestutil.DiffKVStores(storeA, storeB, skipPrefixes[keyName])
if !assert.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare in %q", keyName) {
if !assert.Equal(
t,
len(failedKVAs),
len(failedKVBs),
"unequal sets of key-values to compare in %q",
keyName,
) {
for _, v := range failedKVBs {
t.Logf("store missmatch: %q\n", v)
t.Logf("store mismatch: %q\n", v)
}
t.FailNow()
}
t.Logf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), appKeyA, appKeyB)
if !assert.Equal(t, 0, len(failedKVAs), simtestutil.GetSimulationLog(keyName, app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs)) {
t.Logf(
"compared %d different key/value pairs between %s and %s\n",
len(failedKVAs),
appKeyA,
appKeyB,
)
if !assert.Equal(
t,
0,
len(failedKVAs),
simtestutil.GetSimulationLog(
keyName,
app.SimulationManager().StoreDecoders,
failedKVAs,
failedKVBs,
),
) {
for _, v := range failedKVAs {
t.Logf("store missmatch: %q\n", v)
t.Logf("store mismatch: %q\n", v)
}
t.FailNow()
}
@@ -190,7 +221,10 @@ func TestAppImportExport(t *testing.T) {
}
func TestAppSimulationAfterImport(t *testing.T) {
config, db, appOptions, app := setupSimulationApp(t, "skipping application simulation after import")
config, db, appOptions, app := setupSimulationApp(
t,
"skipping application simulation after import",
)
// Run randomized simulation
stopEarly, simParams, simErr := simulation.SimulateFromSeed(
@@ -226,7 +260,13 @@ func TestAppSimulationAfterImport(t *testing.T) {
fmt.Printf("importing genesis...\n")
newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
newDB, newDir, _, _, err := simtestutil.SetupSimulation(
config,
"leveldb-app-sim-2",
"Simulation-2",
simcli.FlagVerboseValue,
simcli.FlagEnabledValue,
)
require.NoError(t, err, "simulation setup failed")
defer func() {
@@ -234,7 +274,9 @@ func TestAppSimulationAfterImport(t *testing.T) {
require.NoError(t, os.RemoveAll(newDir))
}()
newApp := NewChainApp(log.NewNopLogger(), newDB, nil, true, appOptions, nil, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
newApp := NewChainApp(log.NewNopLogger(), newDB, nil, true, appOptions,
EVMAppOptions,
fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
_, err = newApp.InitChain(&abci.RequestInitChain{
ChainId: SimAppChainID,
@@ -256,11 +298,20 @@ func TestAppSimulationAfterImport(t *testing.T) {
require.NoError(t, err)
}
func setupSimulationApp(t *testing.T, msg string) (simtypes.Config, dbm.DB, simtestutil.AppOptionsMap, *SonrApp) {
func setupSimulationApp(
t *testing.T,
msg string,
) (simtypes.Config, dbm.DB, simtestutil.AppOptionsMap, *ChainApp) {
config := simcli.NewConfigFromFlags()
config.ChainID = SimAppChainID
db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
db, dir, logger, skip, err := simtestutil.SetupSimulation(
config,
"leveldb-app-sim",
"Simulation",
simcli.FlagVerboseValue,
simcli.FlagEnabledValue,
)
if skip {
t.Skip(msg)
}
@@ -275,7 +326,9 @@ func setupSimulationApp(t *testing.T, msg string) (simtypes.Config, dbm.DB, simt
appOptions[flags.FlagHome] = dir // ensure a unique folder
appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue
app := NewChainApp(logger, db, nil, true, appOptions, nil, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
app := NewChainApp(logger, db, nil, true, appOptions,
EVMAppOptions,
fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
return config, db, appOptions, app
}
@@ -304,7 +357,7 @@ func TestAppStateDeterminism(t *testing.T) {
appOptions := viper.New()
if FlagEnableStreamingValue {
m := make(map[string]interface{})
m := make(map[string]any)
m["streaming.abci.keys"] = []string{"*"}
m["streaming.abci.plugin"] = "abci_v1"
m["streaming.abci.stop-node-on-err"] = true
@@ -317,7 +370,7 @@ func TestAppStateDeterminism(t *testing.T) {
for i := 0; i < numSeeds; i++ {
config.Seed += int64(i)
for j := 0; j < numTimesToRunPerSeed; j++ {
for j := range numTimesToRunPerSeed {
var logger log.Logger
if simcli.FlagVerboseValue {
logger = log.NewTestLogger(t)
@@ -326,7 +379,9 @@ func TestAppStateDeterminism(t *testing.T) {
}
db := dbm.NewMemDB()
app := NewChainApp(logger, db, nil, true, appOptions, nil, interBlockCacheOpt(), baseapp.SetChainID(SimAppChainID))
app := NewChainApp(logger, db, nil, true, appOptions,
EVMAppOptions,
interBlockCacheOpt(), baseapp.SetChainID(SimAppChainID))
fmt.Printf(
"running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n",
@@ -337,7 +392,11 @@ func TestAppStateDeterminism(t *testing.T) {
t,
os.Stdout,
app.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtestutil.AppStateFn(
app.AppCodec(),
app.SimulationManager(),
app.DefaultGenesis(),
),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
BlockedAddresses(),
@@ -355,8 +414,15 @@ func TestAppStateDeterminism(t *testing.T) {
if j != 0 {
require.Equal(
t, string(appHashList[0]), string(appHashList[j]),
"non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed,
t,
string(appHashList[0]),
string(appHashList[j]),
"non-determinism in seed %d: %d/%d, attempt: %d/%d\n",
config.Seed,
i+1,
numSeeds,
j+1,
numTimesToRunPerSeed,
)
}
}
Regular → Executable
+128 -35
View File
@@ -44,6 +44,8 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
const chainID = "sonrtest_1-1"
// SetupOptions defines arguments that are passed into `ChainApp` constructor.
type SetupOptions struct {
Logger log.Logger
@@ -56,7 +58,7 @@ func setup(
chainID string,
withGenesis bool,
invCheckPeriod uint,
) (*SonrApp, GenesisState) {
) (*ChainApp, GenesisState) {
db := dbm.NewMemDB()
nodeHome := t.TempDir()
snapshotDir := filepath.Join(nodeHome, "data", "snapshots")
@@ -76,6 +78,7 @@ func setup(
nil,
true,
appOptions,
EVMAppOptions,
bam.SetChainID(chainID),
bam.SetSnapshot(snapshotStore, snapshottypes.SnapshotOptions{KeepRecent: 2}),
)
@@ -86,7 +89,7 @@ func setup(
}
// NewChainAppWithCustomOptions initializes a new ChainApp with custom options.
func NewChainAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOptions) *SonrApp {
func NewChainAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOptions) *ChainApp {
t.Helper()
privVal := mock.NewPV()
@@ -98,7 +101,12 @@ func NewChainAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOpt
// generate genesis account
senderPrivKey := secp256k1.GenPrivKey()
acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0)
acc := authtypes.NewBaseAccount(
senderPrivKey.PubKey().Address().Bytes(),
senderPrivKey.PubKey(),
0,
0,
)
balance := banktypes.Balance{
Address: acc.GetAddress().String(),
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))),
@@ -109,9 +117,16 @@ func NewChainAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOpt
options.DB,
nil, true,
options.AppOpts,
EVMAppOptions,
)
genesisState := app.DefaultGenesis()
genesisState, err = GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, []authtypes.GenesisAccount{acc}, balance)
genesisState, err = GenesisStateWithValSet(
app.AppCodec(),
genesisState,
valSet,
[]authtypes.GenesisAccount{acc},
balance,
)
require.NoError(t, err)
if !isCheckTx {
@@ -135,7 +150,7 @@ func NewChainAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOpt
// Setup initializes a new ChainApp. A Nop logger is set in ChainApp.
func Setup(
t *testing.T,
) *SonrApp {
) *ChainApp {
t.Helper()
privVal := mock.NewPV()
@@ -148,12 +163,17 @@ func Setup(
// generate genesis account
senderPrivKey := secp256k1.GenPrivKey()
acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0)
acc := authtypes.NewBaseAccount(
senderPrivKey.PubKey().Address().Bytes(),
senderPrivKey.PubKey(),
0,
0,
)
balance := banktypes.Balance{
Address: acc.GetAddress().String(),
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))),
}
chainID := "testing"
app := SetupWithGenesisValSet(
t,
valSet,
@@ -175,13 +195,18 @@ func SetupWithGenesisValSet(
genAccs []authtypes.GenesisAccount,
chainID string,
balances ...banktypes.Balance,
) *SonrApp {
) *ChainApp {
t.Helper()
app, genesisState := setup(
t, chainID, true, 5,
)
genesisState, err := GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, genAccs, balances...)
genesisState, err := GenesisStateWithValSet(
app.AppCodec(),
genesisState,
valSet,
genAccs,
balances...)
require.NoError(t, err)
stateBytes, err := json.MarshalIndent(genesisState, "", " ")
@@ -211,14 +236,14 @@ func SetupWithGenesisValSet(
}
// SetupWithEmptyStore set up a chain app instance with empty DB
func SetupWithEmptyStore(t testing.TB) *SonrApp {
app, _ := setup(t, "testing", false, 0)
func SetupWithEmptyStore(t testing.TB) *ChainApp {
app, _ := setup(t, chainID, false, 0)
return app
}
// GenesisStateWithSingleValidator initializes GenesisState with a single validator and genesis accounts
// that also act as delegators.
func GenesisStateWithSingleValidator(t *testing.T, app *SonrApp) GenesisState {
func GenesisStateWithSingleValidator(t *testing.T, app *ChainApp) GenesisState {
t.Helper()
privVal := mock.NewPV()
@@ -231,16 +256,28 @@ func GenesisStateWithSingleValidator(t *testing.T, app *SonrApp) GenesisState {
// generate genesis account
senderPrivKey := secp256k1.GenPrivKey()
acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0)
acc := authtypes.NewBaseAccount(
senderPrivKey.PubKey().Address().Bytes(),
senderPrivKey.PubKey(),
0,
0,
)
balances := []banktypes.Balance{
{
Address: acc.GetAddress().String(),
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))),
Coins: sdk.NewCoins(
sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000)),
),
},
}
genesisState := app.DefaultGenesis()
genesisState, err = GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, []authtypes.GenesisAccount{acc}, balances...)
genesisState, err = GenesisStateWithValSet(
app.AppCodec(),
genesisState,
valSet,
[]authtypes.GenesisAccount{acc},
balances...)
require.NoError(t, err)
return genesisState
@@ -248,11 +285,22 @@ func GenesisStateWithSingleValidator(t *testing.T, app *SonrApp) GenesisState {
// AddTestAddrsIncremental constructs and returns accNum amount of accounts with an
// initial balance of accAmt in random order
func AddTestAddrsIncremental(app *SonrApp, ctx sdk.Context, accNum int, accAmt sdkmath.Int) []sdk.AccAddress {
func AddTestAddrsIncremental(
app *ChainApp,
ctx sdk.Context,
accNum int,
accAmt sdkmath.Int,
) []sdk.AccAddress {
return addTestAddrs(app, ctx, accNum, accAmt, simtestutil.CreateIncrementalAccounts)
}
func addTestAddrs(app *SonrApp, ctx sdk.Context, accNum int, accAmt sdkmath.Int, strategy simtestutil.GenerateAccountStrategy) []sdk.AccAddress {
func addTestAddrs(
app *ChainApp,
ctx sdk.Context,
accNum int,
accAmt sdkmath.Int,
strategy simtestutil.GenerateAccountStrategy,
) []sdk.AccAddress {
testAddrs := strategy(accNum)
bondDenom, err := app.StakingKeeper.BondDenom(ctx)
if err != nil {
@@ -268,7 +316,7 @@ func addTestAddrs(app *SonrApp, ctx sdk.Context, accNum int, accAmt sdkmath.Int,
return testAddrs
}
func initAccountWithCoins(app *SonrApp, ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) {
func initAccountWithCoins(app *ChainApp, ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) {
err := app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, coins)
if err != nil {
panic(err)
@@ -288,11 +336,19 @@ func NewTestNetworkFixture() network.TestFixture {
}
defer os.RemoveAll(dir)
app := NewChainApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(dir), nil)
app := NewChainApp(
log.NewNopLogger(),
dbm.NewMemDB(),
nil,
true,
simtestutil.NewAppOptionsWithFlagHome(dir),
EVMAppOptions,
)
appCtr := func(val network.ValidatorI) servertypes.Application {
return NewChainApp(
val.GetCtx().Logger, dbm.NewMemDB(), nil, true,
simtestutil.NewAppOptionsWithFlagHome(val.GetCtx().Config.RootDir),
EVMAppOptions,
bam.SetPruning(pruningtypes.NewPruningOptionsFromString(val.GetAppConfig().Pruning)),
bam.SetMinGasPrices(val.GetAppConfig().MinGasPrices),
bam.SetChainID(val.GetCtx().Viper.GetString(flags.FlagChainID)),
@@ -312,7 +368,17 @@ func NewTestNetworkFixture() network.TestFixture {
}
// SignAndDeliverWithoutCommit signs and delivers a transaction. No commit
func SignAndDeliverWithoutCommit(t *testing.T, txCfg client.TxConfig, app *bam.BaseApp, msgs []sdk.Msg, fees sdk.Coins, chainID string, accNums, accSeqs []uint64, blockTime time.Time, priv ...cryptotypes.PrivKey) (*abci.ResponseFinalizeBlock, error) {
func SignAndDeliverWithoutCommit(
t *testing.T,
txCfg client.TxConfig,
app *bam.BaseApp,
msgs []sdk.Msg,
fees sdk.Coins,
chainID string,
accNums, accSeqs []uint64,
blockTime time.Time,
priv ...cryptotypes.PrivKey,
) (*abci.ResponseFinalizeBlock, error) {
tx, err := simtestutil.GenSignedMockTx(
rand.New(rand.NewSource(time.Now().UnixNano())),
txCfg,
@@ -366,25 +432,40 @@ func GenesisStateWithValSet(
}
validator := stakingtypes.Validator{
OperatorAddress: sdk.ValAddress(val.Address).String(),
ConsensusPubkey: pkAny,
Jailed: false,
Status: stakingtypes.Bonded,
Tokens: bondAmt,
DelegatorShares: sdkmath.LegacyOneDec(),
Description: stakingtypes.Description{},
UnbondingHeight: int64(0),
UnbondingTime: time.Unix(0, 0).UTC(),
Commission: stakingtypes.NewCommission(sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec()),
OperatorAddress: sdk.ValAddress(val.Address).String(),
ConsensusPubkey: pkAny,
Jailed: false,
Status: stakingtypes.Bonded,
Tokens: bondAmt,
DelegatorShares: sdkmath.LegacyOneDec(),
Description: stakingtypes.Description{},
UnbondingHeight: int64(0),
UnbondingTime: time.Unix(0, 0).UTC(),
Commission: stakingtypes.NewCommission(
sdkmath.LegacyZeroDec(),
sdkmath.LegacyZeroDec(),
sdkmath.LegacyZeroDec(),
),
MinSelfDelegation: sdkmath.ZeroInt(),
}
validators = append(validators, validator)
delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress().String(), sdk.ValAddress(val.Address).String(), sdkmath.LegacyOneDec()))
delegations = append(
delegations,
stakingtypes.NewDelegation(
genAccs[0].GetAddress().String(),
sdk.ValAddress(val.Address).String(),
sdkmath.LegacyOneDec(),
),
)
}
// set validators and delegations
stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations)
stakingGenesis := stakingtypes.NewGenesisState(
stakingtypes.DefaultParams(),
validators,
delegations,
)
genesisState[stakingtypes.ModuleName] = codec.MustMarshalJSON(stakingGenesis)
signingInfos := make([]slashingtypes.SigningInfo, len(valSet.Validators))
@@ -394,13 +475,19 @@ func GenesisStateWithValSet(
ValidatorSigningInfo: slashingtypes.ValidatorSigningInfo{},
}
}
slashingGenesis := slashingtypes.NewGenesisState(slashingtypes.DefaultParams(), signingInfos, nil)
slashingGenesis := slashingtypes.NewGenesisState(
slashingtypes.DefaultParams(),
signingInfos,
nil,
)
genesisState[slashingtypes.ModuleName] = codec.MustMarshalJSON(slashingGenesis)
// add bonded amount to bonded pool module account
balances = append(balances, banktypes.Balance{
Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(),
Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt.MulRaw(int64(len(valSet.Validators))))},
Coins: sdk.Coins{
sdk.NewCoin(sdk.DefaultBondDenom, bondAmt.MulRaw(int64(len(valSet.Validators)))),
},
})
totalSupply := sdk.NewCoins()
@@ -410,7 +497,13 @@ func GenesisStateWithValSet(
}
// update total supply
bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}, []banktypes.SendEnabled{})
bankGenesis := banktypes.NewGenesisState(
banktypes.DefaultGenesisState().Params,
balances,
totalSupply,
[]banktypes.Metadata{},
[]banktypes.SendEnabled{},
)
genesisState[banktypes.ModuleName] = codec.MustMarshalJSON(bankGenesis)
return genesisState, nil
Regular → Executable
+6 -6
View File
@@ -10,26 +10,26 @@ import (
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
)
func (app *SonrApp) GetIBCKeeper() *ibckeeper.Keeper {
func (app *ChainApp) GetIBCKeeper() *ibckeeper.Keeper {
return app.IBCKeeper
}
func (app *SonrApp) GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper {
func (app *ChainApp) GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper {
return app.ScopedIBCKeeper
}
func (app *SonrApp) GetBaseApp() *baseapp.BaseApp {
func (app *ChainApp) GetBaseApp() *baseapp.BaseApp {
return app.BaseApp
}
func (app *SonrApp) GetBankKeeper() bankkeeper.Keeper {
func (app *ChainApp) GetBankKeeper() bankkeeper.Keeper {
return app.BankKeeper
}
func (app *SonrApp) GetStakingKeeper() *stakingkeeper.Keeper {
func (app *ChainApp) GetStakingKeeper() *stakingkeeper.Keeper {
return app.StakingKeeper
}
func (app *SonrApp) GetAccountKeeper() authkeeper.AccountKeeper {
func (app *ChainApp) GetAccountKeeper() authkeeper.AccountKeeper {
return app.AccountKeeper
}
+24
View File
@@ -0,0 +1,24 @@
// Package app provides ERC20 token pair configuration for the Sonr blockchain.
package app
import erc20types "github.com/cosmos/evm/x/erc20/types"
// WSonrTokenContractMainnet is the WrappedToken contract address for mainnet.
// This address represents the ERC20 wrapper for the native token.
const WSonrTokenContractMainnet = "0xD4949664cD82660AaE99bEdc034a0deA8A0bd517"
// WSonrTokenContractTestnet is the WrappedToken contract address for testnet.
// This address represents the ERC20 wrapper for the native token.
const WSonrTokenContractTestnet = "0xD4949664cD82660AaE99bEdc034a0deA8A0bd517"
// SonrETHTokenPairs creates a slice of token pairs that define the mapping between
// native Cosmos SDK coins and their ERC20 representations. This allows for seamless
// conversion between the two token standards within the EVM module.
var SonrETHTokenPairs = []erc20types.TokenPair{
{
Erc20Address: WSonrTokenContractTestnet,
Denom: BaseDenom,
Enabled: true,
ContractOwner: erc20types.OWNER_MODULE,
},
}
Executable
+8
View File
@@ -0,0 +1,8 @@
// Package app contains tool imports to ensure required dependencies are included
// in the module graph even if they're not directly referenced in the code.
// This prevents "go mod tidy" from removing necessary indirect dependencies.
package app
import (
_ "cosmossdk.io/orm"
)
Regular → Executable
+14 -8
View File
@@ -1,3 +1,4 @@
// Package app provides upgrade handling functionality for the Sonr blockchain.
package app
import (
@@ -5,15 +6,19 @@ import (
upgradetypes "cosmossdk.io/x/upgrade/types"
"github.com/sonr-io/snrd/app/upgrades"
"github.com/sonr-io/snrd/app/upgrades/noop"
"github.com/sonr-io/sonr/app/upgrades"
"github.com/sonr-io/sonr/app/upgrades/noop"
)
// Upgrades list of chain upgrades
// Upgrades contains the list of chain upgrades to be applied.
// Each upgrade defines the upgrade name, handler, and store migrations.
var Upgrades = []upgrades.Upgrade{}
// RegisterUpgradeHandlers registers the chain upgrade handlers
func (app *SonrApp) RegisterUpgradeHandlers() {
// RegisterUpgradeHandlers registers the chain upgrade handlers for all defined upgrades.
// It sets up the upgrade handlers with the module manager and configurator,
// and configures the store loader for the current upgrade if applicable.
// If no upgrades are defined, it registers a no-op upgrade for testing purposes.
func (app *ChainApp) RegisterUpgradeHandlers() {
// setupLegacyKeyTables(&app.ParamsKeeper)
if len(Upgrades) == 0 {
// always have a unique upgrade registered for the current version to test in system tests
@@ -22,7 +27,6 @@ func (app *SonrApp) RegisterUpgradeHandlers() {
keepers := upgrades.AppKeepers{
AccountKeeper: &app.AccountKeeper,
DidKeeper: &app.DidKeeper,
ParamsKeeper: &app.ParamsKeeper,
ConsensusParamsKeeper: &app.ConsensusParamsKeeper,
CapabilityKeeper: app.CapabilityKeeper,
@@ -30,7 +34,7 @@ func (app *SonrApp) RegisterUpgradeHandlers() {
Codec: app.appCodec,
GetStoreKey: app.GetKey,
}
app.GetStoreKeys()
// register all upgrade handlers
for _, upgrade := range Upgrades {
app.UpgradeKeeper.SetUpgradeHandler(
@@ -55,7 +59,9 @@ func (app *SonrApp) RegisterUpgradeHandlers() {
// register store loader for current upgrade
for _, upgrade := range Upgrades {
if upgradeInfo.Name == upgrade.UpgradeName {
app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &upgrade.StoreUpgrades)) // nolint:gosec
app.SetStoreLoader(
upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &upgrade.StoreUpgrades),
) // nolint:gosec
break
}
}
Regular → Executable
+9 -2
View File
@@ -1,3 +1,5 @@
// Package noop provides a no-operation upgrade handler for testing and development.
// This upgrade performs no actual state migrations but runs module migrations.
package noop
import (
@@ -8,10 +10,12 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/sonr-io/snrd/app/upgrades"
"github.com/sonr-io/sonr/app/upgrades"
)
// NewUpgrade constructor
// NewUpgrade creates a new no-operation upgrade with the specified semantic version.
// This upgrade is typically used for testing upgrade mechanisms without performing
// actual state changes beyond standard module migrations.
func NewUpgrade(semver string) upgrades.Upgrade {
return upgrades.Upgrade{
UpgradeName: semver,
@@ -23,6 +27,9 @@ func NewUpgrade(semver string) upgrades.Upgrade {
}
}
// CreateUpgradeHandler creates an upgrade handler that performs only module migrations.
// It does not perform any custom upgrade logic, making it suitable for minor version
// upgrades that only require standard module migrations.
func CreateUpgradeHandler(
mm upgrades.ModuleManager,
configurator module.Configurator,
Regular → Executable
+19 -6
View File
@@ -1,3 +1,4 @@
// Package upgrades provides types and interfaces for chain upgrade handling.
package upgrades
import (
@@ -14,12 +15,13 @@ import (
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper"
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
didkeeper "github.com/sonr-io/snrd/x/did/keeper"
)
// AppKeepers holds references to all the keepers needed during chain upgrades.
// This struct is passed to upgrade handlers to provide access to various
// module keepers and core functionality.
type AppKeepers struct {
AccountKeeper *authkeeper.AccountKeeper
DidKeeper *didkeeper.Keeper
ParamsKeeper *paramskeeper.Keeper
ConsensusParamsKeeper *consensusparamkeeper.Keeper
Codec codec.Codec
@@ -27,8 +29,15 @@ type AppKeepers struct {
CapabilityKeeper *capabilitykeeper.Keeper
IBCKeeper *ibckeeper.Keeper
}
// ModuleManager defines the interface for running module migrations during upgrades.
// It provides methods to execute migrations and retrieve the current version map.
type ModuleManager interface {
RunMigrations(ctx context.Context, cfg module.Configurator, fromVM module.VersionMap) (module.VersionMap, error)
RunMigrations(
ctx context.Context,
cfg module.Configurator,
fromVM module.VersionMap,
) (module.VersionMap, error)
GetVersionMap() module.VersionMap
}
@@ -37,10 +46,14 @@ type ModuleManager interface {
// An upgrade must implement this struct, and then set it in the app.go.
// The app.go will then define the handler.
type Upgrade struct {
// Upgrade version name, for the upgrade handler, e.g. `v7`
// UpgradeName is the version name for the upgrade handler, e.g. `v7`.
// This must match the upgrade name in the governance proposal.
UpgradeName string
// CreateUpgradeHandler defines the function that creates an upgrade handler
// CreateUpgradeHandler defines the function that creates an upgrade handler.
// The handler performs the actual upgrade logic when the chain reaches the upgrade height.
CreateUpgradeHandler func(ModuleManager, module.Configurator, *AppKeepers) upgradetypes.UpgradeHandler
StoreUpgrades storetypes.StoreUpgrades
// StoreUpgrades defines any store migrations needed for this upgrade,
// including adding, renaming, or deleting stores.
StoreUpgrades storetypes.StoreUpgrades
}