Skip to content

Commit

Permalink
Implement clawback of airdrop from inactive genesis addresses (#560)
Browse files Browse the repository at this point in the history
* works!

* Update x/claim/types/expected_keepers.go

* use helper functions in existing code

* Fix lint

* Update cmd/osmosisd/cmd/testnetify/cmd.go

* Fix testcase settings

* fix clawback

* fix lint

* Documentation updates

* switch map to array

* Update cmd/osmosisd/cmd/genaccounts.go

Co-authored-by: mattverse <[email protected]>
Co-authored-by: Jacob Gadikian <[email protected]>
Co-authored-by: ValarDragon <[email protected]>
Co-authored-by: Dev Ojha <[email protected]>
  • Loading branch information
5 people authored Nov 19, 2021
1 parent 9d45b64 commit dea840c
Show file tree
Hide file tree
Showing 8 changed files with 87,708 additions and 44 deletions.
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] [output-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. If erroring, ensure to `git lfs pull`
Example:
osmosisd import-genesis-accounts-from-snapshot networks/cosmoshub-3/snapshot.json networks/osmosis-1/ions.json output_address.json
`,
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)
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
4 changes: 2 additions & 2 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 Down
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.
if seq == 0 {
osmoBal := k.bankKeeper.GetBalance(ctx, addr, "uosmo")
ionBal := k.bankKeeper.GetBalance(ctx, addr, "uion")
err = k.distrKeeper.FundCommunityPool(ctx, sdk.NewCoins(osmoBal, ionBal), addr)
if err != nil {
return err
}
}
}
return nil
}

Expand Down
Loading

0 comments on commit dea840c

Please sign in to comment.