Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion proto/terra/taxexemption/v1/genesis.proto
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
syntax = "proto3";
package terra.taxexemption.v1;

import "gogoproto/gogo.proto";
import "terra/taxexemption/v1/taxexemption.proto";

option go_package = "github.com/classic-terra/core/v3/x/taxexemption/types";

// GenesisState defines the taxexemption module's genesis state.
message GenesisState {

repeated Zone zone_list = 1 [
(gogoproto.moretags) = "yaml:\"zone_list\"",
(gogoproto.nullable) = false
];
repeated AddressesByZone addresses_by_zone = 2 [
(gogoproto.moretags) = "yaml:\"addresses_by_zone\"",
(gogoproto.nullable) = false
];
}

message AddressesByZone {
string zone = 1 [(gogoproto.moretags) = "yaml:\"zone\""];
repeated string addresses = 2 [(gogoproto.moretags) = "yaml:\"addresses\""];
}
2 changes: 1 addition & 1 deletion tests/interchaintest/oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,8 @@ func isOraclePrioritized(tx []Tx) bool {
}
}
return lastOracleIdx == -1 || lastOracleIdx < firstNonOracleIdx

}

func isOracleTx(tx Tx) bool {
for _, event := range tx.Events {
for _, attr := range event.Attributes {
Expand Down
105 changes: 97 additions & 8 deletions x/taxexemption/genesis.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,118 @@
package taxexemption

import (
"fmt"
"slices"
"sort"

"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/classic-terra/core/v3/x/taxexemption/keeper"
"github.com/classic-terra/core/v3/x/taxexemption/types"
)

// NewGenesisState creates a new GenesisState object
func NewGenesisState() *types.GenesisState {
return &types.GenesisState{}
}

// DefaultGenesisState gets raw genesis raw message for testing
func DefaultGenesisState() *types.GenesisState {
return &types.GenesisState{}
return &types.GenesisState{
ZoneList: []types.Zone{},
AddressesByZone: []types.AddressesByZone{},
}
}

// ValidateGenesis validates the provided oracle genesis state to ensure the
// expected invariants holds. (i.e. params in correct bounds, no duplicate validators)
func ValidateGenesis(data *types.GenesisState) error {
if len(data.ZoneList) != len(data.AddressesByZone) {
return types.ErrZoneLengthInvalid
}

zones := []string{}
for _, zone := range data.ZoneList {
zones = append(zones, zone.Name)
}

for _, addressesByZone := range data.AddressesByZone {
if !slices.Contains(zones, addressesByZone.Zone) {
return types.ErrZoneNotExist
}

for _, address := range addressesByZone.Addresses {
if _, err := sdk.AccAddressFromBech32(address); err != nil {
return err
}
}
}
return nil
}

// InitGenesis initializes default parameters
// and the keeper's address to pubkey map
func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, data *types.GenesisState) {}
func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, data *types.GenesisState) {
// Set the zone list
for _, zone := range data.ZoneList {
keeper.AddTaxExemptionZone(ctx, zone)
}

// Set the addresses by zone
for _, addressesByZone := range data.AddressesByZone {
for _, address := range addressesByZone.Addresses {
keeper.AddTaxExemptionAddress(ctx, addressesByZone.Zone, address)
}
}
}

// ExportGenesis writes the current store values
// to a genesis file, which can be imported again
// with InitGenesis
func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) (data *types.GenesisState) {
return types.NewGenesisState()
zonePrefix := prefix.NewStore(ctx.KVStore(keeper.StoreKey()), types.TaxExemptionZonePrefix)
iterator := zonePrefix.Iterator(nil, nil)
defer iterator.Close()

var zones []types.Zone
zoneAddresses := make(map[string][]string)
var addresesByZone []types.AddressesByZone

for ; iterator.Valid(); iterator.Next() {
var zone types.Zone
keeper.Codec().MustUnmarshal(iterator.Value(), &zone)
zones = append(zones, zone)
zoneAddresses[zone.Name] = []string{}
}

addressesPrefix := prefix.NewStore(ctx.KVStore(keeper.StoreKey()), types.TaxExemptionListPrefix)
iterator = addressesPrefix.Iterator(nil, nil)
defer iterator.Close()

for ; iterator.Valid(); iterator.Next() {
zoneName := string(iterator.Value())
if _, ok := zoneAddresses[zoneName]; ok {
zoneAddresses[zoneName] = append(zoneAddresses[zoneName], string(iterator.Key()))
}
}

var zoneNames []string
for zoneName := range zoneAddresses {
zoneNames = append(zoneNames, zoneName)
}
sort.Strings(zoneNames)

for _, zoneName := range zoneNames {
addresses := zoneAddresses[zoneName]
addresesByZone = append(addresesByZone, types.AddressesByZone{
Zone: zoneName,
Addresses: addresses,
})
}

state := &types.GenesisState{
ZoneList: zones,
AddressesByZone: addresesByZone,
}
err := ValidateGenesis(state)
if err != nil {
panic(fmt.Sprint("error validate genesis: ", err))
}
return state
}
142 changes: 130 additions & 12 deletions x/taxexemption/genesis_test.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,156 @@
package taxexemption_test

import (
"slices"
"testing"

taxexemption "github.com/classic-terra/core/v3/x/taxexemption"
ultil "github.com/classic-terra/core/v3/x/taxexemption/keeper"
util "github.com/classic-terra/core/v3/x/taxexemption/keeper"
"github.com/classic-terra/core/v3/x/taxexemption/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)

func TestNewGenesisState(t *testing.T) {
genesis := types.NewGenesisState()
require.NotNil(t, genesis)
}

func TestDefaultGenesisState(t *testing.T) {
genesis := taxexemption.DefaultGenesisState()
require.NotNil(t, genesis)
}

func TestInitAndExportGenesis_Empty(t *testing.T) {
// Setup mock context & keeper (simple zero value for now)
input := ultil.CreateTestInput(t)
input := util.CreateTestInput(t)
k := input.TaxExemptionKeeper
ctx := sdk.Context{}

// Initialize genesis with empty state
genesis := taxexemption.NewGenesisState()
genesis := taxexemption.DefaultGenesisState()
require.NotNil(t, genesis)

taxexemption.InitGenesis(ctx, k, genesis)
taxexemption.InitGenesis(input.Ctx, k, genesis)

// Export genesis and check that it's not nil and matches default state
exported := taxexemption.ExportGenesis(ctx, k)
exported := taxexemption.ExportGenesis(input.Ctx, k)
require.NotNil(t, exported)
}

func TestInitAndExportGenesis_NonEmpty(t *testing.T) {
// Setup mock context & keeper (simple zero value for now)
input := util.CreateTestInput(t)
k := input.TaxExemptionKeeper

addresses := []string{
util.Addrs[0].String(),
util.Addrs[1].String(),
}
slices.Sort(addresses)
// Initialize genesis with empty state
genesis := taxexemption.DefaultGenesisState()
genesis.ZoneList = []types.Zone{
{
Name: "test-zone",
Incoming: true,
Outgoing: true,
CrossZone: true,
},
}
genesis.AddressesByZone = []types.AddressesByZone{
{
Zone: "test-zone",
Addresses: addresses,
},
}
require.NotNil(t, genesis)

taxexemption.InitGenesis(input.Ctx, k, genesis)

// Export genesis and check that it's not nil and matches default state
exportedGenesis := taxexemption.ExportGenesis(input.Ctx, k)
require.NotNil(t, exportedGenesis)

require.Equal(t, genesis, exportedGenesis)
}

func TestValidateGenesis_Success(t *testing.T) {
// Initialize genesis with empty state
genesis := taxexemption.DefaultGenesisState()
genesis.ZoneList = []types.Zone{
{
Name: "test-zone",
Incoming: true,
Outgoing: true,
CrossZone: true,
},
}
genesis.AddressesByZone = []types.AddressesByZone{
{
Zone: "test-zone",
Addresses: []string{
util.Addrs[0].String(),
util.Addrs[1].String(),
},
},
}
require.NotNil(t, genesis)

err := taxexemption.ValidateGenesis(genesis)
require.NoError(t, err)
}

func TestValidateGenesis_Failure(t *testing.T) {
// Case 1: Zone length mismatch
genesis := taxexemption.DefaultGenesisState()
genesis.ZoneList = []types.Zone{
{
Name: "test-zone",
Incoming: true,
Outgoing: true,
CrossZone: true,
},
}
genesis.AddressesByZone = []types.AddressesByZone{}

err := taxexemption.ValidateGenesis(genesis)
require.ErrorContains(t, err, "length of zone list and addresses by zone must be equal")

// Case 2: Invalid address
genesis = taxexemption.DefaultGenesisState()
genesis.ZoneList = []types.Zone{
{
Name: "test-zone",
Incoming: true,
Outgoing: true,
CrossZone: true,
},
}
genesis.AddressesByZone = []types.AddressesByZone{
{
Zone: "test-zone",
Addresses: []string{
"invalid-address",
},
},
}

err = taxexemption.ValidateGenesis(genesis)
require.ErrorContains(t, err, "decoding bech32 failed")

// Case 3: Zone not exist
genesis = taxexemption.DefaultGenesisState()
genesis.ZoneList = []types.Zone{
{
Name: "test-zone",
Incoming: true,
Outgoing: true,
CrossZone: true,
},
}
genesis.AddressesByZone = []types.AddressesByZone{
{
Zone: "non-existent-zone",
Addresses: []string{
util.Addrs[0].String(),
},
},
}

err = taxexemption.ValidateGenesis(genesis)
require.ErrorContains(t, err, "zone not exist")
}
17 changes: 12 additions & 5 deletions x/taxexemption/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,10 @@ func (k Keeper) ListTaxExemptionAddresses(c sdk.Context, req *types.QueryTaxExem

// Create an iterator over the store
pageRes, err := query.FilteredPaginate(sub, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) {
if accumulate {
zoneName := string(value)
if req.ZoneName == "" || zoneName == req.ZoneName {
addresses = append(addresses, string(key))
}
if req.ZoneName == "" && accumulate {
addresses = append(addresses, string(key))
} else if string(value) == req.ZoneName {
addresses = append(addresses, string(key))
}
return true, nil
})
Expand All @@ -322,3 +321,11 @@ func (k Keeper) ListTaxExemptionAddresses(c sdk.Context, req *types.QueryTaxExem

return addresses, pageRes, nil
}

func (k Keeper) StoreKey() storetypes.StoreKey {
return k.storeKey
}

func (k Keeper) Codec() codec.BinaryCodec {
return k.cdc
}
6 changes: 4 additions & 2 deletions x/taxexemption/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,8 +544,10 @@ func TestListTaxExemptionAddresses(t *testing.T) {

// Test listing addresses for specific zone
req = &types.QueryTaxExemptionAddressRequest{
ZoneName: zones[0].Name,
Pagination: nil,
ZoneName: zones[0].Name,
Pagination: &sdkquery.PageRequest{
Limit: 2,
},
}
listedAddresses, pageRes, err = input.TaxExemptionKeeper.ListTaxExemptionAddresses(input.Ctx, req)
require.NoError(t, err)
Expand Down
10 changes: 10 additions & 0 deletions x/taxexemption/keeper/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,16 @@ func TestTaxExemptionAddressList(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 2, len(res.Addresses))

// Case 4: Query addresses with zone and pagination
res, err = querier.TaxExemptionAddressList(ctx, &types.QueryTaxExemptionAddressRequest{
ZoneName: zoneName1,
Pagination: &query.PageRequest{
Limit: 1,
},
})
require.NoError(t, err)
require.Equal(t, 1, len(res.Addresses))

// Case 5: Query addresses with non-existent zone
res, err = querier.TaxExemptionAddressList(ctx, &types.QueryTaxExemptionAddressRequest{
ZoneName: "zone3",
Expand Down
Loading