Skip to content

Commit

Permalink
add min amount for rate limit
Browse files Browse the repository at this point in the history
  • Loading branch information
GNaD13 committed Aug 15, 2023
1 parent c1c3bb8 commit d198a8c
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 30 deletions.
7 changes: 6 additions & 1 deletion proto/centauri/ratelimit/v1beta1/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@ import "gogoproto/gogo.proto";
import "cosmos_proto/cosmos.proto";

// Params holds parameters for the mint module.
message Params {}
message Params {
string min_rate_limit_amount = 1 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
}
10 changes: 8 additions & 2 deletions x/ratelimit/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func NewKeeper(
tfmwKeeper tfmwkeeper.Keeper,
authority string,
) *Keeper {
// set KeyTable if it has not already been set
if !ps.HasKeyTable() {
ps = ps.WithKeyTable(types.ParamKeyTable())
}

return &Keeper{
cdc: cdc,
storeKey: key,
Expand All @@ -55,8 +60,9 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
}

// GetParams get all parameters as types.Params
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
return types.NewParams()
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
k.paramstore.GetParamSet(ctx, &params)
return params
}

// SetParams set the params
Expand Down
9 changes: 5 additions & 4 deletions x/ratelimit/keeper/rate_limit.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ func EmitTransferDeniedEvent(ctx sdk.Context, reason, denom, channelId string, d
}

// Adds an amount to the flow in either the SEND or RECV direction
func (k Keeper) UpdateFlow(rateLimit types.RateLimit, direction types.PacketDirection, amount math.Int) error {
func (k Keeper) UpdateFlow(ctx sdk.Context, rateLimit types.RateLimit, direction types.PacketDirection, amount math.Int) error {
minRateLimitAmount := k.GetParams(ctx).MinRateLimitAmount
switch direction {
case types.PACKET_SEND:
return rateLimit.Flow.AddOutflow(amount, *rateLimit.Quota)
return rateLimit.Flow.AddOutflow(amount, *rateLimit.Quota, minRateLimitAmount)
case types.PACKET_RECV:
return rateLimit.Flow.AddInflow(amount, *rateLimit.Quota)
return rateLimit.Flow.AddInflow(amount, *rateLimit.Quota, minRateLimitAmount)
default:
return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "invalid packet direction (%s)", direction.String())
}
Expand Down Expand Up @@ -75,7 +76,7 @@ func (k Keeper) CheckRateLimitAndUpdateFlow(
return false, nil
}
// Update the flow object with the change in amount
if err := k.UpdateFlow(rateLimit, direction, amount); err != nil {
if err := k.UpdateFlow(ctx, rateLimit, direction, amount); err != nil {
// If the rate limit was exceeded, emit an event
EmitTransferDeniedEvent(ctx, types.EventRateLimitExceeded, denom, channelId, direction, amount, err)
return false, err
Expand Down
258 changes: 251 additions & 7 deletions x/ratelimit/relay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestKeeperTestSuite(t *testing.T) {

func (suite *RateLimitTestSuite) TestReceiveIBCToken() {
var (
transferAmount = sdk.NewInt(1000000000)
transferAmount = sdk.NewInt(1_000_000_000_000)
// when transfer via sdk transfer from A (module) -> B (contract)
ibcDenom = "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878"
nativeDenom = "ppica"
Expand All @@ -68,7 +68,16 @@ func (suite *RateLimitTestSuite) TestReceiveIBCToken() {
originalChainABalance := suite.chainA.AllBalances(suite.chainA.SenderAccount.GetAddress())
originalChainBBalance := suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress())

msg := transfertypes.NewMsgTransfer(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, nativeTokenSendOnChainA, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), timeoutHeight, 0, "")
msg := transfertypes.NewMsgTransfer(
path.EndpointA.ChannelConfig.PortID,
path.EndpointA.ChannelID,
nativeTokenSendOnChainA,
suite.chainA.SenderAccount.GetAddress().String(),
suite.chainB.SenderAccount.GetAddress().String(),
timeoutHeight,
0,
"",
)
_, err = suite.chainA.SendMsgs(msg)
suite.Require().NoError(err)
suite.Require().NoError(err, path.EndpointB.UpdateClient())
Expand Down Expand Up @@ -100,8 +109,8 @@ func (suite *RateLimitTestSuite) TestReceiveIBCToken() {
msgAddRateLimit := ratelimittypes.MsgAddRateLimit{
Denom: nativeDenom,
ChannelId: path.EndpointB.ChannelID,
MaxPercentSend: sdk.NewInt(5),
MaxPercentRecv: sdk.NewInt(5),
MaxPercentSend: sdk.NewInt(5), // 50_000_000_000 > minRateLimitAmount(10_000_000_000) => RateLimit = 50_000_000_000
MaxPercentRecv: sdk.NewInt(5), // 50_000_000_000 > minRateLimitAmount(10_000_000_000) => RateLimit = 50_000_000_000
DurationHours: 1,
}
err = chainBRateLimitKeeper.AddRateLimit(suite.chainB.GetContext(), &msgAddRateLimit)
Expand Down Expand Up @@ -155,7 +164,7 @@ func (suite *RateLimitTestSuite) TestReceiveIBCToken() {

func (suite *RateLimitTestSuite) TestSendIBCToken() {
var (
transferAmount = sdk.NewInt(1000000000)
transferAmount = sdk.NewInt(1_000_000_000_000)
// when transfer via sdk transfer from A (module) -> B (contract)
ibcDenom = "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878"
nativeDenom = "ppica"
Expand Down Expand Up @@ -211,8 +220,8 @@ func (suite *RateLimitTestSuite) TestSendIBCToken() {
msgAddRateLimit := ratelimittypes.MsgAddRateLimit{
Denom: nativeDenom,
ChannelId: path.EndpointB.ChannelID,
MaxPercentSend: sdk.NewInt(5),
MaxPercentRecv: sdk.NewInt(5),
MaxPercentSend: sdk.NewInt(5), // 50_000_000_000 > minRateLimitAmount(10_000_000_000) => RateLimit = 50_000_000_000
MaxPercentRecv: sdk.NewInt(5), // 50_000_000_000 > minRateLimitAmount(10_000_000_000) => RateLimit = 50_000_000_000
DurationHours: 1,
}
err = chainBRateLimitKeeper.AddRateLimit(suite.chainB.GetContext(), &msgAddRateLimit)
Expand Down Expand Up @@ -259,3 +268,238 @@ func (suite *RateLimitTestSuite) TestSendIBCToken() {
balances := suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress())
suite.Require().Equal(expBalance, balances)
}

func (suite *RateLimitTestSuite) TestReceiveIBCTokenWithMinRateLimitAmount() {
var (
transferAmount = sdk.NewInt(100_000_000_000)
// when transfer via sdk transfer from A (module) -> B (contract)
ibcDenom = "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878"
nativeDenom = "ppica"
nativeTokenSendOnChainA = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount)
nativeTokenReceiveOnChainB = sdk.NewCoin(nativeDenom, transferAmount)
timeoutHeight = clienttypes.NewHeight(1, 110)
expChainABalanceDiff = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount)
)

suite.SetupTest() // reset

path := NewTransferPath(suite.chainA, suite.chainB)
suite.coordinator.Setup(path)

// Add parachain token info
chainBtransMiddlewareKeeper := suite.chainB.TransferMiddleware()
err := chainBtransMiddlewareKeeper.AddParachainIBCInfo(suite.chainB.GetContext(), ibcDenom, path.EndpointB.ChannelID, nativeDenom, sdk.DefaultBondDenom)
suite.Require().NoError(err)

originalChainABalance := suite.chainA.AllBalances(suite.chainA.SenderAccount.GetAddress())
originalChainBBalance := suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress())

msg := transfertypes.NewMsgTransfer(
path.EndpointA.ChannelConfig.PortID,
path.EndpointA.ChannelID,
nativeTokenSendOnChainA,
suite.chainA.SenderAccount.GetAddress().String(),
suite.chainB.SenderAccount.GetAddress().String(),
timeoutHeight,
0,
"",
)
_, err = suite.chainA.SendMsgs(msg)
suite.Require().NoError(err)
suite.Require().NoError(err, path.EndpointB.UpdateClient())

// then
suite.Require().Equal(1, len(suite.chainA.PendingSendPackets))
suite.Require().Equal(0, len(suite.chainB.PendingSendPackets))

// and when relay to chain B and handle Ack on chain A
err = suite.coordinator.RelayAndAckPendingPackets(path)
suite.Require().NoError(err)

// then
suite.Require().Equal(0, len(suite.chainA.PendingSendPackets))
suite.Require().Equal(0, len(suite.chainB.PendingSendPackets))

// and source chain balance was decreased
newChainABalance := suite.chainA.AllBalances(suite.chainA.SenderAccount.GetAddress())
suite.Require().Equal(originalChainABalance.Sub(expChainABalanceDiff), newChainABalance)

// and dest chain balance contains voucher
expBalance := originalChainBBalance.Add(nativeTokenReceiveOnChainB)
gotBalance := suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress())

suite.Require().Equal(expBalance, gotBalance)

// add rate limit
chainBRateLimitKeeper := suite.chainB.RateLimit()
msgAddRateLimit := ratelimittypes.MsgAddRateLimit{
Denom: nativeDenom,
ChannelId: path.EndpointB.ChannelID,
MaxPercentSend: sdk.NewInt(5), // 5_000_000_000 < minRateLimitAmount(10_000_000_000) => RateLimit = 10_000_000_000
MaxPercentRecv: sdk.NewInt(5), // 5_000_000_000 < minRateLimitAmount(10_000_000_000) => RateLimit = 10_000_000_000
DurationHours: 1,
}
err = chainBRateLimitKeeper.AddRateLimit(suite.chainB.GetContext(), &msgAddRateLimit)
suite.Require().NoError(err)

// send from A to B
transferAmount = sdk.NewInt(10_000_000_000)
nativeTokenSendOnChainA = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount)
msg = transfertypes.NewMsgTransfer(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, nativeTokenSendOnChainA, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), timeoutHeight, 0, "")
_, err = suite.chainA.SendMsgs(msg)
suite.Require().NoError(err)
suite.Require().NoError(err, path.EndpointB.UpdateClient())

// then
suite.Require().Equal(1, len(suite.chainA.PendingSendPackets))
suite.Require().Equal(0, len(suite.chainB.PendingSendPackets))

// and when relay to chain B and handle Ack on chain A
err = suite.coordinator.RelayAndAckPendingPackets(path)
suite.Require().NoError(err)

// then
suite.Require().Equal(0, len(suite.chainA.PendingSendPackets))
suite.Require().Equal(0, len(suite.chainB.PendingSendPackets))

expBalance = expBalance.Add(sdk.NewCoin(nativeDenom, transferAmount))
gotBalance = suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress())
suite.Require().Equal(expBalance, gotBalance)

// send 1 more time
_, err = suite.chainA.SendMsgs(msg)
suite.Require().NoError(err)
suite.Require().NoError(err, path.EndpointB.UpdateClient())

// then
suite.Require().Equal(1, len(suite.chainA.PendingSendPackets))
suite.Require().Equal(0, len(suite.chainB.PendingSendPackets))

// and when relay to chain B and handle Ack on chain A
err = suite.coordinator.RelayAndAckPendingPackets(path)
suite.Require().NoError(err)

// then
suite.Require().Equal(0, len(suite.chainA.PendingSendPackets))
suite.Require().Equal(0, len(suite.chainB.PendingSendPackets))

// not receive token because catch the threshold => balances have no change
gotBalance = suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress())
suite.Require().Equal(expBalance, gotBalance)
}

func (suite *RateLimitTestSuite) TestSendIBCTokenWithMinRateLimitAmount() {
var (
transferAmount = sdk.NewInt(100_000_000_000)
// when transfer via sdk transfer from A (module) -> B (contract)
ibcDenom = "ibc/C053D637CCA2A2BA030E2C5EE1B28A16F71CCB0E45E8BE52766DC1B241B77878"
nativeDenom = "ppica"
nativeTokenSendOnChainA = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount)
nativeTokenReceiveOnChainB = sdk.NewCoin(nativeDenom, transferAmount)
timeoutHeight = clienttypes.NewHeight(1, 110)
expChainABalanceDiff = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount)
)

suite.SetupTest() // reset

path := NewTransferPath(suite.chainA, suite.chainB)
suite.coordinator.Setup(path)

// Add parachain token info
chainBtransMiddlewareKeeper := suite.chainB.TransferMiddleware()
err := chainBtransMiddlewareKeeper.AddParachainIBCInfo(suite.chainB.GetContext(), ibcDenom, path.EndpointB.ChannelID, nativeDenom, sdk.DefaultBondDenom)
suite.Require().NoError(err)

originalChainABalance := suite.chainA.AllBalances(suite.chainA.SenderAccount.GetAddress())
originalChainBBalance := suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress())

msg := transfertypes.NewMsgTransfer(
path.EndpointA.ChannelConfig.PortID,
path.EndpointA.ChannelID,
nativeTokenSendOnChainA,
suite.chainA.SenderAccount.GetAddress().String(),
suite.chainB.SenderAccount.GetAddress().String(),
timeoutHeight,
0,
"",
)
_, err = suite.chainA.SendMsgs(msg)
suite.Require().NoError(err)
suite.Require().NoError(err, path.EndpointB.UpdateClient())

// then
suite.Require().Equal(1, len(suite.chainA.PendingSendPackets))
suite.Require().Equal(0, len(suite.chainB.PendingSendPackets))

// and when relay to chain B and handle Ack on chain A
err = suite.coordinator.RelayAndAckPendingPackets(path)
suite.Require().NoError(err)

// then
suite.Require().Equal(0, len(suite.chainA.PendingSendPackets))
suite.Require().Equal(0, len(suite.chainB.PendingSendPackets))

// and source chain balance was decreased
newChainABalance := suite.chainA.AllBalances(suite.chainA.SenderAccount.GetAddress())
suite.Require().Equal(originalChainABalance.Sub(expChainABalanceDiff), newChainABalance)

// and dest chain balance contains voucher
expBalance := originalChainBBalance.Add(nativeTokenReceiveOnChainB)
gotBalance := suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress())

suite.Require().Equal(expBalance, gotBalance)

originalChainBBalance = gotBalance
// add rate limit 5%
chainBRateLimitKeeper := suite.chainB.RateLimit()
msgAddRateLimit := ratelimittypes.MsgAddRateLimit{
Denom: nativeDenom,
ChannelId: path.EndpointB.ChannelID,
MaxPercentSend: sdk.NewInt(5), // 5_000_000_000 < minRateLimitAmount(10_000_000_000) => RateLimit = 10_000_000_000
MaxPercentRecv: sdk.NewInt(5), // 5_000_000_000 < minRateLimitAmount(10_000_000_000) => RateLimit = 10_000_000_000
DurationHours: 1,
}
err = chainBRateLimitKeeper.AddRateLimit(suite.chainB.GetContext(), &msgAddRateLimit)
suite.Require().NoError(err)

// send from B to A
transferAmount = sdk.NewInt(10_000_000_000)
nativeTokenSendOnChainB := sdk.NewCoin(nativeDenom, transferAmount)
msg = transfertypes.NewMsgTransfer(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, nativeTokenSendOnChainB, suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String(), timeoutHeight, 0, "")
_, err = suite.chainB.SendMsgs(msg)
suite.Require().NoError(err)
suite.Require().NoError(err, path.EndpointA.UpdateClient())

// then
suite.Require().Equal(1, len(suite.chainB.PendingSendPackets))
suite.Require().Equal(0, len(suite.chainA.PendingSendPackets))

// and when relay to chain B and handle Ack on chain A
err = suite.coordinator.RelayAndAckPendingPacketsReverse(path)
suite.Require().NoError(err)

// then
suite.Require().Equal(0, len(suite.chainA.PendingSendPackets))
suite.Require().Equal(0, len(suite.chainB.PendingSendPackets))

expBalance = originalChainBBalance.Sub(nativeTokenSendOnChainB)
gotBalance = suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress())
suite.Require().Equal(expBalance, gotBalance)

// send 1 more time
_, err = suite.chainB.SendMsgsWithExpPass(false, msg)
suite.Require().Error(err) // catch the threshold so should not be sent

// SignAndDeliver calls app.Commit()
suite.chainB.NextBlock()

// increment sequence for successful transaction execution
err = suite.chainB.SenderAccount.SetSequence(suite.chainB.SenderAccount.GetSequence() + 1)
suite.Require().NoError(err)

suite.chainB.Coordinator.IncrementTime()

// not receive token because catch the threshold => balances have no change
balances := suite.chainB.AllBalances(suite.chainB.SenderAccount.GetAddress())
suite.Require().Equal(expBalance, balances)
}
8 changes: 4 additions & 4 deletions x/ratelimit/types/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ func NewFlow(channelValue math.Int) Flow {

// Adds an amount to the rate limit's flow after an incoming packet was received
// Returns an error if the new inflow will cause the rate limit to exceed its quota
func (f *Flow) AddInflow(amount math.Int, quota Quota) error {
func (f *Flow) AddInflow(amount math.Int, quota Quota, minRateLimit math.Int) error {
netInflow := f.Inflow.Sub(f.Outflow).Add(amount)

if quota.CheckExceedsQuota(PACKET_RECV, netInflow, f.ChannelValue) {
if quota.CheckExceedsQuota(PACKET_RECV, netInflow, f.ChannelValue, minRateLimit) {
return errorsmod.Wrapf(ErrQuotaExceeded,
"Inflow exceeds quota - Net Inflow: %v, Channel Value: %v, Threshold: %v%%",
netInflow, f.ChannelValue, quota.MaxPercentRecv)
Expand All @@ -33,10 +33,10 @@ func (f *Flow) AddInflow(amount math.Int, quota Quota) error {

// Adds an amount to the rate limit's flow after a packet was sent
// Returns an error if the new outflow will cause the rate limit to exceed its quota
func (f *Flow) AddOutflow(amount math.Int, quota Quota) error {
func (f *Flow) AddOutflow(amount math.Int, quota Quota, minRateLimit math.Int) error {
netOutflow := f.Outflow.Sub(f.Inflow).Add(amount)

if quota.CheckExceedsQuota(PACKET_SEND, netOutflow, f.ChannelValue) {
if quota.CheckExceedsQuota(PACKET_SEND, netOutflow, f.ChannelValue, minRateLimit) {
return errorsmod.Wrapf(ErrQuotaExceeded,
"Outflow exceeds quota - Net Outflow: %v, Channel Value: %v, Threshold: %v%%",
netOutflow, f.ChannelValue, quota.MaxPercentSend)
Expand Down
Loading

0 comments on commit d198a8c

Please sign in to comment.