Skip to content

[BUG] No Limit on Exchange Rate Tuples in Oracle Votes #269

@ngapaxs

Description

@ngapaxs

Bug Description

Found that validators can submit unlimited exchange rates in a single oracle vote. The code only checks the string length (max 4096 chars) but doesn't limit how many denoms you can pack into that string.

Location: x/oracle/keeper/msg_server.go and x/oracle/types/msgs.go

The ValidateBasic function checks:

  • String can't be longer than 4096 characters
  • Must have at least one rate

But it doesn't check how many exchange rate tuples you actually submit. So you can pack hundreds or thousands of denoms into one vote as long as the string fits in 4096 chars.

How to reproduce

The validation code in x/oracle/types/msgs.go:

func (msg MsgAggregateExchangeRateVote) ValidateBasic() error {
    // Only checks string length
    if len(msg.ExchangeRates) > 4096 {
        return errors.Wrap(sdkerrors.ErrInvalidRequest, 
            "exchange rates string can not exceed 4096 characters")
    }

    exchangeRates, err := ParseExchangeRateTuples(msg.ExchangeRates)
    // ... validates each rate ...
    
    // But NO CHECK on len(exchangeRates) here!
}

Then in msg_server.go it just loops through all of them:

for _, exchangeRate := range exchangeRates {
    // processes every denom without limit
}

I tested submitting 1000 denoms - all accepted without error.

Impact

A validator could spam the network by submitting tons of exchange rates in each vote. This would:

  • Slow down vote processing (loops through all denoms)
  • Increase vote tally calculation time
  • Potentially DoS the oracle module

Not critical but definitely a resource exhaustion vector.

Test Case

func TestUnboundedExchangeRateTuples(t *testing.T) {
	// Try submitting 1000 denoms
	rates := make([]string, 1000)
	for i := 0; i < 1000; i++ {
		rates[i] = fmt.Sprintf("1.5DENOM%d", i)
	}
	exchangeRateStr := strings.Join(rates, ",")

	parsed, err := types.ParseExchangeRateTuples(exchangeRateStr)
	require.NoError(t, err)
	
	t.Logf("Accepted %d exchange rate tuples without bounds check", len(parsed))
}
$ cd /root/kiichain && /root/kiichain/go/bin/go test -v -tags=test ./x/oracle/keeper -run TestUnboundedExchangeRateTuples

=== RUN   TestUnboundedExchangeRateTuples
    test_unbounded_rates_real_test.go:24: Accepted 1000 exchange rate tuples without bounds check
--- PASS: TestUnboundedExchangeRateTuples (0.00s)
PASS
ok      github.com/kiichain/kiichain/v7/x/oracle/keeper 0.162s

Environment

  • Kiichain version: v7 (latest commit)
  • Test environment: Ubuntu Linux
  • Files affected:
    • x/oracle/types/msgs.go
    • x/oracle/keeper/msg_server.go

Suggested Fix

Add a maximum limit check in ValidateBasic():

const MaxExchangeRateTuples = 50 // reasonable limit based on typical whitelist size

func (msg MsgAggregateExchangeRateVote) ValidateBasic() error {
    // ... existing string length check ...
    
    exchangeRates, err := ParseExchangeRateTuples(msg.ExchangeRates)
    if err != nil {
        return err
    }
    
    // NEW: Check tuple count
    if len(exchangeRates) > MaxExchangeRateTuples {
        return fmt.Errorf("too many exchange rate tuples: %d (max %d)", 
            len(exchangeRates), MaxExchangeRateTuples)
    }
    
    // ... rest of validation ...
}

This prevents resource exhaustion while still allowing reasonable use cases.

Declaration

  • Not a duplicate
  • Tested and confirmed
  • Not AI generated

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions