Skip to content

Commit

Permalink
feat: Add option to designate Reward Recipient to Lock and Incentives (
Browse files Browse the repository at this point in the history
…#5281)

* Add Reward Recepient to lock

* Add changelog

* Store receiver if receiver is lock owner

* Add defense in depth

* Debuggoing..

* Add debug

* More logs

* Fixed

* Update x/incentives/keeper/distribute.go

* Update x/lockup/keeper/lock.go

* Update x/lockup/keeper/lock_test.go

Co-authored-by: Adam Tucker <[email protected]>

* Update x/lockup/types/msgs.go

Co-authored-by: Adam Tucker <[email protected]>

* Update x/lockup/types/msgs.go

Co-authored-by: Adam Tucker <[email protected]>

* Adams review

* Adam's review

* Fix test

* Add romans comment

---------

Co-authored-by: Adam Tucker <[email protected]>
  • Loading branch information
2 people authored and pysel committed Jun 6, 2023
1 parent 96a8492 commit ff09a72
Show file tree
Hide file tree
Showing 20 changed files with 1,199 additions and 170 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#4830](https://github.com/osmosis-labs/osmosis/pull/4830) Add gas cost when we AddToGaugeRewards, linearly increase with coins to add
* [#4886](https://github.com/osmosis-labs/osmosis/pull/4886) Implement MsgSplitRouteSwapExactAmountIn and MsgSplitRouteSwapExactAmountOut that supports route splitting.
* [#5000](https://github.com/osmosis-labs/osmosis/pull/5000) osmomath.Power panics for base < 1 to temporarily restrict broken logic for such base.
* [#5281](https://github.com/osmosis-labs/osmosis/pull/5281) Add option to designate Reward Recipient to Lock and Incentives.
* [#4827] (https://github.com/osmosis-labs/osmosis/pull/4827) Protorev: Change highest liquidity pool updating from weekly to daily and change dev fee payout from weekly to after every trade.

### Misc Improvements
Expand Down
5 changes: 5 additions & 0 deletions proto/osmosis/lockup/lock.proto
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ message PeriodLock {
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// Reward Receiver Address is the address that would be receiving rewards for
// the incentives for the lock. This is set to owner by default and can be
// changed via separate msg.
string reward_receiver_address = 6
[ (gogoproto.moretags) = "yaml:\"reward_receiver_address\"" ];
}

// LockQueryType defines the type of the lock query that can
Expand Down
13 changes: 12 additions & 1 deletion proto/osmosis/lockup/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ service Msg {
// MsgEditLockup edits the existing lockups by lock ID
rpc ExtendLockup(MsgExtendLockup) returns (MsgExtendLockupResponse);
rpc ForceUnlock(MsgForceUnlock) returns (MsgForceUnlockResponse);
// SetRewardReceiverAddress edits the reward receiver for the given lock ID
rpc SetRewardReceiverAddress(MsgSetRewardReceiverAddress)
returns (MsgSetRewardReceiverAddressResponse);
}

message MsgLockTokens {
Expand Down Expand Up @@ -95,4 +98,12 @@ message MsgForceUnlock {
];
}

message MsgForceUnlockResponse { bool success = 1; }
message MsgForceUnlockResponse { bool success = 1; }

message MsgSetRewardReceiverAddress {
string owner = 1 [ (gogoproto.moretags) = "yaml:\"owner\"" ];
uint64 lockID = 2;
string reward_receiver = 3
[ (gogoproto.moretags) = "yaml:\"reward_receiver\"" ];
}
message MsgSetRewardReceiverAddressResponse { bool success = 1; }
47 changes: 29 additions & 18 deletions x/incentives/keeper/distribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,53 +158,58 @@ func (k Keeper) FilteredLocksDistributionEst(ctx sdk.Context, gauge types.Gauge,
// distributionInfo stores all of the information for pent up sends for rewards distributions.
// This enables us to lower the number of events and calls to back.
type distributionInfo struct {
nextID int
lockOwnerAddrToID map[string]int
idToBech32Addr []string
idToDecodedAddr []sdk.AccAddress
idToDistrCoins []sdk.Coins
nextID int
lockOwnerAddrToID map[string]int
lockOwnerAddrToRewardReceiver map[string]string
idToBech32Addr []string
idToDecodedRewardReceiverAddr []sdk.AccAddress
idToDistrCoins []sdk.Coins
}

// newDistributionInfo creates a new distributionInfo struct
func newDistributionInfo() distributionInfo {
return distributionInfo{
nextID: 0,
lockOwnerAddrToID: make(map[string]int),
idToBech32Addr: []string{},
idToDecodedAddr: []sdk.AccAddress{},
idToDistrCoins: []sdk.Coins{},
nextID: 0,
lockOwnerAddrToID: make(map[string]int),
lockOwnerAddrToRewardReceiver: make(map[string]string),
idToBech32Addr: []string{},
idToDecodedRewardReceiverAddr: []sdk.AccAddress{},
idToDistrCoins: []sdk.Coins{},
}
}

// addLockRewards adds the provided rewards to the lockID mapped to the provided owner address.
func (d *distributionInfo) addLockRewards(owner string, rewards sdk.Coins) error {
func (d *distributionInfo) addLockRewards(owner, rewardReceiver string, rewards sdk.Coins) error {
// if we have already added current lock owner's info to distribution Info, simply add reward.
if id, ok := d.lockOwnerAddrToID[owner]; ok {
oldDistrCoins := d.idToDistrCoins[id]
d.idToDistrCoins[id] = rewards.Add(oldDistrCoins...)
} else {
} else { // if this is a new owner that we have not added to distributionInfo yet,
// add according information to the distributionInfo maps.
id := d.nextID
d.nextID += 1
d.lockOwnerAddrToID[owner] = id
decodedOwnerAddr, err := sdk.AccAddressFromBech32(owner)
decodedRewardReceiverAddr, err := sdk.AccAddressFromBech32(rewardReceiver)
if err != nil {
return err
}
d.idToBech32Addr = append(d.idToBech32Addr, owner)
d.idToDecodedAddr = append(d.idToDecodedAddr, decodedOwnerAddr)
d.idToBech32Addr = append(d.idToBech32Addr, rewardReceiver)
d.idToDecodedRewardReceiverAddr = append(d.idToDecodedRewardReceiverAddr, decodedRewardReceiverAddr)
d.idToDistrCoins = append(d.idToDistrCoins, rewards)
}
return nil
}

// doDistributionSends utilizes provided distributionInfo to send coins from the module account to various recipients.
func (k Keeper) doDistributionSends(ctx sdk.Context, distrs *distributionInfo) error {
numIDs := len(distrs.idToDecodedAddr)
numIDs := len(distrs.idToDecodedRewardReceiverAddr)
if numIDs > 0 {
ctx.Logger().Debug(fmt.Sprintf("Beginning distribution to %d users", numIDs))
// send rewards from the gauge to the reward receiver address
err := k.bk.SendCoinsFromModuleToManyAccounts(
ctx,
types.ModuleName,
distrs.idToDecodedAddr,
distrs.idToDecodedRewardReceiverAddr,
distrs.idToDistrCoins)
if err != nil {
return err
Expand Down Expand Up @@ -324,7 +329,13 @@ func (k Keeper) distributeInternal(
continue
}
// update the amount for that address
err := distrInfo.addLockRewards(lock.Owner, distrCoins)
rewardReceiver := lock.RewardReceiverAddress

// if the reward receiver stored in state is an empty string, it indicates that the owner is the reward receiver.
if rewardReceiver == "" {
rewardReceiver = lock.Owner
}
err := distrInfo.addLockRewards(lock.Owner, rewardReceiver, distrCoins)
if err != nil {
return nil, err
}
Expand Down
79 changes: 75 additions & 4 deletions x/incentives/keeper/distribute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ func (s *KeeperTestSuite) TestDistribute() {
noRewardCoins := sdk.Coins{}
oneKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 1000)}
twoKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 2000)}
threeKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 3000)}
fiveKRewardCoins := sdk.Coins{sdk.NewInt64Coin(defaultRewardDenom, 5000)}
tests := []struct {
name string
users []userLocks
gauges []perpGaugeDesc
expectedRewards []sdk.Coins
name string
users []userLocks
gauges []perpGaugeDesc
changeRewardReceiver []changeRewardReceiver
expectedRewards []sdk.Coins
}{
// gauge 1 gives 3k coins. three locks, all eligible. 1k coins per lock.
// 1k should go to oneLockupUser and 2k to twoLockupUser.
Expand Down Expand Up @@ -78,13 +80,82 @@ func (s *KeeperTestSuite) TestDistribute() {
gauges: []perpGaugeDesc{noRewardGauge, defaultGauge},
expectedRewards: []sdk.Coins{oneKRewardCoins, twoKRewardCoins},
},
// gauge 1 gives 3k coins. three locks, all eligible. 1k coins per lock.
// we change oneLockupUser lock's reward recepient to the twoLockupUser
// none should go to oneLockupUser and 3k to twoLockupUser.
{
name: "Change Reward Receiver: One user with one lockup, another user with two lockups, single default gauge",
users: []userLocks{oneLockupUser, twoLockupUser},
gauges: []perpGaugeDesc{defaultGauge},
changeRewardReceiver: []changeRewardReceiver{
// change first lock's receiver address to the second account
{
lockId: 1,
newReceiverAccIndex: 1,
},
},
expectedRewards: []sdk.Coins{sdk.NewCoins(), threeKRewardCoins},
},
// gauge 1 gives 3k coins. three locks, all eligible. 1k coins per lock.
// We change oneLockupUser's reward recepient to twoLockupUser, twoLockupUser's reward recepient to OneLockupUser.
// Rewards should be reversed to the original test case, 2k should go to oneLockupUser and 1k to twoLockupUser.
{
name: "Change Reward Receiver: One user with one lockup, another user with two lockups, single default gauge",
users: []userLocks{oneLockupUser, twoLockupUser},
gauges: []perpGaugeDesc{defaultGauge},
changeRewardReceiver: []changeRewardReceiver{
// change first lock's receiver address to the second account
{
lockId: 1,
newReceiverAccIndex: 1,
},
{
lockId: 2,
newReceiverAccIndex: 0,
},
{
lockId: 3,
newReceiverAccIndex: 0,
},
},
expectedRewards: []sdk.Coins{twoKRewardCoins, oneKRewardCoins},
},
// gauge 1 gives 3k coins. three locks, all eligible.
// gauge 2 gives 3k coins. one lock, to twoLockupUser.
// Change all of oneLockupUser's reward recepient to twoLockupUser, vice versa.
// Rewards should be reversed, 5k should to oneLockupUser and 1k to twoLockupUser.
{
name: "Change Reward Receiver: One user with one lockup (default gauge), another user with two lockups (double length gauge)",
users: []userLocks{oneLockupUser, twoLockupUser},
gauges: []perpGaugeDesc{defaultGauge, doubleLengthGauge},
changeRewardReceiver: []changeRewardReceiver{
{
lockId: 1,
newReceiverAccIndex: 1,
},
{
lockId: 2,
newReceiverAccIndex: 0,
},
{
lockId: 3,
newReceiverAccIndex: 0,
},
},
expectedRewards: []sdk.Coins{fiveKRewardCoins, oneKRewardCoins},
},
}
for _, tc := range tests {
s.SetupTest()
// setup gauges and the locks defined in the above tests, then distribute to them
gauges := s.SetupGauges(tc.gauges, defaultLPDenom)
addrs := s.SetupUserLocks(tc.users)

// set up reward receiver if not nil
if len(tc.changeRewardReceiver) != 0 {
s.SetupChangeRewardReceiver(tc.changeRewardReceiver, addrs)
}

_, err := s.App.IncentivesKeeper.Distribute(s.Ctx, gauges)
s.Require().NoError(err)
// check expected rewards against actual rewards received
Expand Down
15 changes: 15 additions & 0 deletions x/incentives/keeper/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ type perpGaugeDesc struct {
rewardAmount sdk.Coins
}

type changeRewardReceiver struct {
newReceiverAccIndex int
lockId uint64
}

// setupAddr takes a balance, prefix, and address number. Then returns the respective account address byte array.
// If prefix is left blank, it will be replaced with a random prefix.
func (s *KeeperTestSuite) setupAddr(addrNum int, prefix string, balance sdk.Coins) sdk.AccAddress {
Expand Down Expand Up @@ -83,6 +88,16 @@ func (s *KeeperTestSuite) SetupUserLocks(users []userLocks) (accs []sdk.AccAddre
return
}

func (s *KeeperTestSuite) SetupChangeRewardReceiver(changeRewardReceivers []changeRewardReceiver, accs []sdk.AccAddress) {
for _, changeRewardReceiver := range changeRewardReceivers {
lock, err := s.App.LockupKeeper.GetLockByID(s.Ctx, changeRewardReceiver.lockId)
s.Require().NoError(err)

err = s.App.LockupKeeper.SetLockRewardReceiverAddress(s.Ctx, changeRewardReceiver.lockId, lock.OwnerAddress(), accs[changeRewardReceiver.newReceiverAccIndex].String())
s.Require().NoError(err)
}
}

// SetupUserSyntheticLocks takes an array of user locks and creates synthetic locks based on this array, then returns the respective account address byte array.
func (s *KeeperTestSuite) SetupUserSyntheticLocks(users []userLocks) (accs []sdk.AccAddress) {
accs = make([]sdk.AccAddress, len(users))
Expand Down
2 changes: 1 addition & 1 deletion x/lockup/keeper/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func benchmarkResetLogic(b *testing.B, numLockups int) {
addr := addrs[r.Int()%numAccts]
simCoins := sdk.NewCoins(sdk.NewCoin(denom, sdk.NewInt(r.Int63n(100))))
duration := time.Duration(r.Intn(1*60*60*24*7)) * time.Second
lock := lockuptypes.NewPeriodLock(uint64(i+1), addr, duration, time.Time{}, simCoins)
lock := lockuptypes.NewPeriodLock(uint64(i+1), addr, addr.String(), duration, time.Time{}, simCoins)
locks[i] = lock
}

Expand Down
77 changes: 42 additions & 35 deletions x/lockup/keeper/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,28 @@ var (
LastLockId: 10,
Locks: []types.PeriodLock{
{
ID: 1,
Owner: acc1.String(),
Duration: time.Second,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 10000000)},
ID: 1,
Owner: acc1.String(),
RewardReceiverAddress: "",
Duration: time.Second,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 10000000)},
},
{
ID: 2,
Owner: acc1.String(),
Duration: time.Hour,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 15000000)},
ID: 2,
Owner: acc1.String(),
RewardReceiverAddress: acc2.String(),
Duration: time.Hour,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 15000000)},
},
{
ID: 3,
Owner: acc2.String(),
Duration: time.Minute,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 5000000)},
ID: 3,
Owner: acc2.String(),
RewardReceiverAddress: acc1.String(),
Duration: time.Minute,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 5000000)},
},
},
}
Expand Down Expand Up @@ -90,32 +93,36 @@ func TestExportGenesis(t *testing.T) {
require.Equal(t, genesisExported.LastLockId, uint64(11))
require.Equal(t, genesisExported.Locks, []types.PeriodLock{
{
ID: 1,
Owner: acc1.String(),
Duration: time.Second,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 10000000)},
ID: 1,
Owner: acc1.String(),
RewardReceiverAddress: "",
Duration: time.Second,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 10000000)},
},
{
ID: 11,
Owner: acc2.String(),
Duration: time.Second * 5,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 5000000)},
ID: 11,
Owner: acc2.String(),
RewardReceiverAddress: "",
Duration: time.Second * 5,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 5000000)},
},
{
ID: 3,
Owner: acc2.String(),
Duration: time.Minute,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 5000000)},
ID: 3,
Owner: acc2.String(),
RewardReceiverAddress: acc1.String(),
Duration: time.Minute,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 5000000)},
},
{
ID: 2,
Owner: acc1.String(),
Duration: time.Hour,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 15000000)},
ID: 2,
Owner: acc1.String(),
RewardReceiverAddress: acc2.String(),
Duration: time.Hour,
EndTime: time.Time{},
Coins: sdk.Coins{sdk.NewInt64Coin("foo", 15000000)},
},
})
}
Expand Down
Loading

0 comments on commit ff09a72

Please sign in to comment.