Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement clawback of airdrop from inactive genesis addresses #560

Merged
merged 12 commits into from
Nov 19, 2021
166 changes: 128 additions & 38 deletions cmd/osmosisd/cmd/genaccounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import (
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/server"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
appparams "github.com/osmosis-labs/osmosis/app/params"
claimtypes "github.com/osmosis-labs/osmosis/x/claim/types"
)

Expand Down Expand Up @@ -183,6 +183,116 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa
return cmd
}

func GetOsmoSnapshot(inputFile string) (Snapshot, error) {
snapshotJSON, err := os.Open(inputFile)
if err != nil {
return Snapshot{}, err
}
defer snapshotJSON.Close()

byteValue, err := ioutil.ReadAll(snapshotJSON)
if err != nil {
return Snapshot{}, err
}

snapshot := Snapshot{}
err = json.Unmarshal(byteValue, &snapshot)
if err != nil {
return Snapshot{}, err
}
return snapshot, nil
}

func GetIonSnapshot(inputFile string) (map[string]int64, error) {
ionJSON, err := os.Open(inputFile)
if err != nil {
return nil, err
}
defer ionJSON.Close()
byteValue2, _ := ioutil.ReadAll(ionJSON)

var ionAmts map[string]int64
err = json.Unmarshal(byteValue2, &ionAmts)
if err != nil {
return nil, err
}
return ionAmts, nil
}

func CosmosToOsmoAddress(cosmosAddr string) (string, error) {
_, bz, err := bech32.DecodeAndConvert(cosmosAddr)
if err != nil {
return "", err
}

convertedAddr, err := bech32.ConvertAndEncode("osmo", bz)
if err != nil {
panic(err)
}

return convertedAddr, nil
}

func GetAirdropAccountsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "get-airdrop-accounts [input-snapshot-file] [input-ions-file]",
Short: "Get list of all accounts that are being airdropped to at genesis",
Long: `Get list of all accounts that are being airdropped to at genesis
Both OSMO and ION recipients

Example:
osmosisd import-genesis-accounts-from-snapshot ../snapshot.json ../ions.json ./address.txt
`,
ValarDragon marked this conversation as resolved.
Show resolved Hide resolved
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
airdropAddrs := map[string]bool{}

// Read snapshot file
snapshot, err := GetOsmoSnapshot(args[0])
if err != nil {
return err
}

// Read ions file
ionAmts, err := GetIonSnapshot(args[1])
if err != nil {
return err
}

for _, acc := range snapshot.Accounts {
if !acc.OsmoBalance.Equal(sdk.ZeroInt()) {
osmoAddr, err := CosmosToOsmoAddress(acc.AtomAddress)
if err != nil {
return err
}

airdropAddrs[osmoAddr] = true
}
}

for addr := range ionAmts {
ionAddr, err := CosmosToOsmoAddress(addr)
mattverse marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

airdropAddrs[ionAddr] = true
}

airdropAddrsJSON, err := json.Marshal(airdropAddrs)
if err != nil {
return err
}
err = ioutil.WriteFile(args[2], airdropAddrsJSON, 0644)
return err
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func ImportGenesisAccountsFromSnapshotCmd(defaultNodeHome string) *cobra.Command {
cmd := &cobra.Command{
Use: "import-genesis-accounts-from-snapshot [input-snapshot-file] [input-ions-file]",
Expand Down Expand Up @@ -223,29 +333,13 @@ Example:
}

// Read snapshot file
snapshotInput := args[0]
snapshotJSON, err := os.Open(snapshotInput)
if err != nil {
return err
}
defer snapshotJSON.Close()
byteValue, _ := ioutil.ReadAll(snapshotJSON)
snapshot := Snapshot{}
err = json.Unmarshal(byteValue, &snapshot)
snapshot, err := GetOsmoSnapshot(args[0])
if err != nil {
return err
}

// Read ions file
ionInput := args[1]
ionJSON, err := os.Open(ionInput)
if err != nil {
return err
}
defer ionJSON.Close()
byteValue2, _ := ioutil.ReadAll(ionJSON)
var ionAmts map[string]int64
err = json.Unmarshal(byteValue2, &ionAmts)
ionAmts, err := GetIonSnapshot(args[1])
if err != nil {
return err
}
Expand All @@ -267,17 +361,15 @@ Example:
}

for addr, amt := range ionAmts {
setCosmosBech32Prefixes()
address, err := sdk.AccAddressFromBech32(addr)
address, err := CosmosToOsmoAddress(addr)
if err != nil {
return err
}
appparams.SetAddressPrefixes()

if val, ok := nonAirdropAccs[address.String()]; ok {
nonAirdropAccs[address.String()] = val.Add(sdk.NewCoin("uion", sdk.NewInt(amt).MulRaw(1_000_000)))
if val, ok := nonAirdropAccs[address]; ok {
nonAirdropAccs[address] = val.Add(sdk.NewCoin("uion", sdk.NewInt(amt).MulRaw(1_000_000)))
} else {
nonAirdropAccs[address.String()] = sdk.NewCoins(sdk.NewCoin("uion", sdk.NewInt(amt).MulRaw(1_000_000)))
nonAirdropAccs[address] = sdk.NewCoins(sdk.NewCoin("uion", sdk.NewInt(amt).MulRaw(1_000_000)))
}
}

Expand All @@ -293,18 +385,12 @@ Example:

// for each account in the snapshot
for _, acc := range snapshot.Accounts {
// set atom bech32 prefixes
setCosmosBech32Prefixes()

// read address from snapshot
address, err := sdk.AccAddressFromBech32(acc.AtomAddress)
// convert cosmos address to osmo address
address, err := CosmosToOsmoAddress(acc.AtomAddress)
if err != nil {
return err
}

// set osmo bech32 prefixes
appparams.SetAddressPrefixes()

// skip accounts with 0 balance
if !acc.OsmoBalanceBase.IsPositive() {
continue
Expand All @@ -318,29 +404,33 @@ Example:
liquidAmount := normalizedOsmoBalance.Mul(sdk.MustNewDecFromStr("0.2")).TruncateInt() // 20% of airdrop amount
liquidCoins := sdk.NewCoins(sdk.NewCoin(genesisParams.NativeCoinMetadatas[0].Base, liquidAmount))

if coins, ok := nonAirdropAccs[address.String()]; ok {
if coins, ok := nonAirdropAccs[address]; ok {
liquidCoins = liquidCoins.Add(coins...)
delete(nonAirdropAccs, address.String())
delete(nonAirdropAccs, address)
}

liquidBalances = append(liquidBalances, banktypes.Balance{
Address: address.String(),
Address: address,
Coins: liquidCoins,
})

// claimable balances
claimableAmount := normalizedOsmoBalance.Mul(sdk.MustNewDecFromStr("0.8")).TruncateInt()

claimRecords = append(claimRecords, claimtypes.ClaimRecord{
Address: address.String(),
Address: address,
InitialClaimableAmount: sdk.NewCoins(sdk.NewCoin(genesisParams.NativeCoinMetadatas[0].Base, claimableAmount)),
ActionCompleted: []bool{false, false, false, false},
})

claimModuleAccountBalance = claimModuleAccountBalance.Add(claimableAmount)

// Add the new account to the set of genesis accounts
baseAccount := authtypes.NewBaseAccount(address, nil, 0, 0)
sdkaddr, err := sdk.AccAddressFromBech32(address)
if err != nil {
return err
}
baseAccount := authtypes.NewBaseAccount(sdkaddr, nil, 0, 0)
if err := baseAccount.Validate(); err != nil {
return fmt.Errorf("failed to validate new genesis account: %w", err)
}
Expand Down
1 change: 1 addition & 0 deletions cmd/osmosisd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) {
genutilcli.ValidateGenesisCmd(osmosis.ModuleBasics),
AddGenesisAccountCmd(osmosis.DefaultNodeHome),
ExportAirdropSnapshotCmd(),
GetAirdropAccountsCmd(),
ExportDeriveBalancesCmd(),
PrepareGenesisCmd(osmosis.DefaultNodeHome, osmosis.ModuleBasics),
ImportGenesisAccountsFromSnapshotCmd(osmosis.DefaultNodeHome),
Expand Down
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ require (
github.com/lib/pq v1.2.0 // indirect
github.com/libp2p/go-buffer-pool v0.0.2 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect
github.com/minio/highwayhash v1.0.1 // indirect
Expand Down Expand Up @@ -106,7 +106,7 @@ require (
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/text v0.3.6 // indirect
gopkg.in/ini.v1 v1.61.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

replace (
Expand All @@ -116,5 +116,4 @@ replace (
github.com/tendermint/tm-db => github.com/osmosis-labs/tm-db v0.6.5-0.20210911033928-ba9154613417
google.golang.org/grpc => google.golang.org/grpc v1.33.2


)
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,9 @@ github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
Expand Down Expand Up @@ -875,8 +876,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
49 changes: 49 additions & 0 deletions x/claim/keeper/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,55 @@ func (k Keeper) EndAirdrop(ctx sdk.Context) error {
return err
}
k.clearInitialClaimables(ctx)

err = k.ClawbackAirdrop(ctx)
if err != nil {
return err
}
return nil
}

// ClawbackAirdrop implements prop 32 by clawing back all the OSMO and IONs from airdrop
// recipient accounts with a sequence number of 0
func (k Keeper) ClawbackAirdrop(ctx sdk.Context) error {
for _, bechAddr := range types.AirdropAddrs {
addr, err := sdk.AccAddressFromBech32(bechAddr)
if err != nil {
return err
}

acc := k.accountKeeper.GetAccount(ctx, addr)
// if account is nil, do nothing.
if acc == nil {
continue
}
seq, err := k.accountKeeper.GetSequence(ctx, addr)
if err != nil {
return err
}
// When sequence number is 0, _and_ from an airdrop account,
// clawback all the uosmo and uion to community pool.
// There is an edge case here, where if the address has otherwise been sent uosmo or uion
// but never made any tx, that excess sent would also get sent to the community pool.
// This is viewed as an edge case, that the text of Osmosis proposal 32 indicates should
// be done via sending these excess funds to the community pool.
//
// Proposal text to go off of: https://www.mintscan.io/osmosis/proposals/32
// ***Reminder***
// 'Unclaimed' tokens are defined as being in wallets which have a Sequence Number = 0,
// which means the address has NOT performed a single action during the 6 month airdrop claim window.
// ******CLAWBACK PROPOSED FRAMEWORK******
// TLDR -- Send ALL unclaimed ION & OSMO back to the community pool
// and prune those inactive wallets from current state.
ValarDragon marked this conversation as resolved.
Show resolved Hide resolved
if seq == 0 {
osmoBal := k.bankKeeper.GetBalance(ctx, addr, "uosmo")
ionBal := k.bankKeeper.GetBalance(ctx, addr, "uion")
mattverse marked this conversation as resolved.
Show resolved Hide resolved
err = k.distrKeeper.FundCommunityPool(ctx, sdk.NewCoins(osmoBal, ionBal), addr)
if err != nil {
return err
}
}
}
return nil
}

Expand Down
Loading