-
Notifications
You must be signed in to change notification settings - Fork 580
/
transfer_authorization.go
150 lines (121 loc) · 5.14 KB
/
transfer_authorization.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package types
import (
"math/big"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/authz"
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
host "github.com/cosmos/ibc-go/v7/modules/core/24-host"
)
var _ authz.Authorization = &TransferAuthorization{}
// maxUint256 is the maximum value for a 256 bit unsigned integer.
var maxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
// NewTransferAuthorization creates a new TransferAuthorization object.
func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization {
return &TransferAuthorization{
Allocations: allocations,
}
}
// MsgTypeURL implements Authorization.MsgTypeURL.
func (a TransferAuthorization) MsgTypeURL() string {
return sdk.MsgTypeURL(&MsgTransfer{})
}
// Accept implements Authorization.Accept.
func (a TransferAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptResponse, error) {
msgTransfer, ok := msg.(*MsgTransfer)
if !ok {
return authz.AcceptResponse{}, sdkerrors.Wrap(sdkerrors.ErrInvalidType, "type mismatch")
}
for index, allocation := range a.Allocations {
if !(allocation.SourceChannel == msgTransfer.SourceChannel && allocation.SourcePort == msgTransfer.SourcePort) {
continue
}
if !isAllowedAddress(ctx, msgTransfer.Receiver, allocation.AllowList) {
return authz.AcceptResponse{}, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "not allowed receiver address for transfer")
}
// If the spend limit is set to the MaxUint256 sentinel value, do not subtract the amount from the spend limit.
if allocation.SpendLimit.AmountOf(msgTransfer.Token.Denom).Equal(UnboundedSpendLimit()) {
return authz.AcceptResponse{Accept: true, Delete: false, Updated: nil}, nil
}
limitLeft, isNegative := allocation.SpendLimit.SafeSub(msgTransfer.Token)
if isNegative {
return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "requested amount is more than spend limit")
}
if limitLeft.IsZero() {
a.Allocations = append(a.Allocations[:index], a.Allocations[index+1:]...)
if len(a.Allocations) == 0 {
return authz.AcceptResponse{Accept: true, Delete: true}, nil
}
return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{
Allocations: a.Allocations,
}}, nil
}
a.Allocations[index] = Allocation{
SourcePort: allocation.SourcePort,
SourceChannel: allocation.SourceChannel,
SpendLimit: limitLeft,
AllowList: allocation.AllowList,
}
return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{
Allocations: a.Allocations,
}}, nil
}
return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "requested port and channel allocation does not exist")
}
// ValidateBasic implements Authorization.ValidateBasic.
func (a TransferAuthorization) ValidateBasic() error {
if len(a.Allocations) == 0 {
return sdkerrors.Wrap(ErrInvalidAuthorization, "allocations cannot be empty")
}
foundChannels := make(map[string]bool, 0)
for _, allocation := range a.Allocations {
if _, found := foundChannels[allocation.SourceChannel]; found {
return sdkerrors.Wrapf(channeltypes.ErrInvalidChannel, "duplicate source channel ID: %s", allocation.SourceChannel)
}
foundChannels[allocation.SourceChannel] = true
if allocation.SpendLimit == nil {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "spend limit cannot be nil")
}
if err := allocation.SpendLimit.Validate(); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, err.Error())
}
if err := host.PortIdentifierValidator(allocation.SourcePort); err != nil {
return sdkerrors.Wrap(err, "invalid source port ID")
}
if err := host.ChannelIdentifierValidator(allocation.SourceChannel); err != nil {
return sdkerrors.Wrap(err, "invalid source channel ID")
}
found := make(map[string]bool, 0)
for i := 0; i < len(allocation.AllowList); i++ {
if found[allocation.AllowList[i]] {
return sdkerrors.Wrapf(ErrInvalidAuthorization, "duplicate entry in allow list %s")
}
found[allocation.AllowList[i]] = true
}
}
return nil
}
// isAllowedAddress returns a boolean indicating if the receiver address is valid for transfer.
// gasCostPerIteration gas is consumed for each iteration.
func isAllowedAddress(ctx sdk.Context, receiver string, allowedAddrs []string) bool {
if len(allowedAddrs) == 0 {
return true
}
gasCostPerIteration := ctx.KVGasConfig().IterNextCostFlat
for _, addr := range allowedAddrs {
ctx.GasMeter().ConsumeGas(gasCostPerIteration, "transfer authorization")
if addr == receiver {
return true
}
}
return false
}
// UnboundedSpendLimit returns the sentinel value that can be used
// as the amount for a denomination's spend limit for which spend limit updating
// should be disabled. Please note that using this sentinel value means that a grantee
// will be granted the privilege to do ICS20 token transfers for the total amount
// of the denomination available at the granter's account.
func UnboundedSpendLimit() sdkmath.Int {
return sdk.NewIntFromBigInt(maxUint256)
}