This commit is contained in:
John Saigle 2024-04-26 14:47:26 -04:00 committed by GitHub
commit ab9b884ee6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 424 additions and 24 deletions

View File

@ -0,0 +1,57 @@
package governor
// FlowCancelTokenList Returns a list of `tokenConfigEntry`s representing tokens that can 'Flow Cancel'. This means that incoming transfers
// that use these tokens can reduce the 'daily limit' of the Governor configured for the destination chain.
// The list of tokens was generated by grepping the file `generated_mainnet_tokens.go` for "USDC", "USDT", and "DAI".
//
// Note that the field `symbol` is unused. It is retained in this file only for convenience.
func FlowCancelTokenList() []tokenConfigEntry {
return []tokenConfigEntry{
// USDC variants
{chain: 2, addr: "000000000000000000000000bcca60bb61934080951369a648fb03df4f96263c", symbol: "aUSDC"},
{chain: 4, addr: "0000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3", symbol: "axlUSDC"},
{chain: 5, addr: "0000000000000000000000001a13f4ca1d028320a707d99520abfefca3998b7f", symbol: "amUSDC"},
{chain: 5, addr: "000000000000000000000000750e4c4984a9e0f12978ea6742bc1c5d248f40ed", symbol: "axlUSDC"},
{chain: 6, addr: "000000000000000000000000fab550568c688d5d8a52c7d794cb93edc26ec0ec", symbol: "axlUSDC"},
{chain: 6, addr: "000000000000000000000000a7d7079b0fead91f3e65f86e8915cb59c1a4c664", symbol: "USDC.e"},
{chain: 10, addr: "0000000000000000000000001b6382dbdea11d97f24495c9a90b7c88469134a4", symbol: "axlUSDC"},
{chain: 10, addr: "00000000000000000000000028a92dde19d9989f39a49905d7c9c2fac7799bdf", symbol: "USDC"},
{chain: 10, addr: "00000000000000000000000027e611fd27b276acbd5ffd632e5eaebec9761e40", symbol: "DAI+USDC"},
{chain: 13, addr: "000000000000000000000000754288077d0ff82af7a5317c7cb8c444d421d103", symbol: "oUSDC"},
{chain: 14, addr: "000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f405215", symbol: "axlUSDC"},
{chain: 16, addr: "000000000000000000000000ca01a1d0993565291051daff390892518acfad3a", symbol: "axlUSDC"},
{chain: 23, addr: "000000000000000000000000625e7708f30ca75bfd92586e17077590c60eb4cd", symbol: "aArbUSDC"},
{chain: 24, addr: "000000000000000000000000625e7708f30ca75bfd92586e17077590c60eb4cd", symbol: "aOptUSDC"},
{chain: 30, addr: "000000000000000000000000eb466342c4d449bc9f53a865d5cb90586f405215", symbol: "axlUSDC"},
// USDT variants
{chain: 1, addr: "b7db4e83eb727f1187bd7a50303f5b4e4e943503da8571ad6564a51131504792", symbol: ""},
{chain: 1, addr: "ce010e60afedb22717bd63192f54145a3f965a33bb82d2c7029eb2ce1e208264", symbol: "USDT"},
{chain: 2, addr: "000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7", symbol: "USDT"},
{chain: 4, addr: "00000000000000000000000055d398326f99059ff775485246999027b3197955", symbol: "USDT"},
{chain: 5, addr: "000000000000000000000000c2132d05d31c914a87c6611c10748aeb04b58e8f", symbol: "USDT"},
{chain: 6, addr: "0000000000000000000000009702230a8ea53601f5cd2dc00fdbc13d4df4a8c7", symbol: "USDt"},
{chain: 6, addr: "000000000000000000000000c7198437980c041c805a1edcba50c1ce5db95118", symbol: "USDT.e"},
{chain: 8, addr: "000000000000000000000000000000000000000000000000000000000004c5c1", symbol: "USDt"},
{chain: 9, addr: "0000000000000000000000004988a896b1227218e4a686fde5eabdcabd91571f", symbol: "USDT"},
{chain: 10, addr: "000000000000000000000000cc1b99ddac1a33c201a742a1851662e87bc7f22c", symbol: "USDT"},
{chain: 10, addr: "000000000000000000000000049d68029688eabf473097a2fc38ef61633a3c7a", symbol: "fUSDT"},
{chain: 13, addr: "000000000000000000000000cee8faf64bb97a73bb51e115aa89c17ffa8dd167", symbol: "oUSDT"},
{chain: 16, addr: "000000000000000000000000efaeee334f0fd1712f9a8cc375f427d9cdd40d73", symbol: "USDT"},
{chain: 16, addr: "000000000000000000000000ffffffffea09fb06d082fd1275cd48b191cbcd1d", symbol: "xcUSDT"},
{chain: 23, addr: "000000000000000000000000fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", symbol: "USDT"},
{chain: 24, addr: "00000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58", symbol: "USDT"},
// DAI variants (DAI+USDC is included under the USDC list above)
{chain: 2, addr: "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", symbol: "DAI"},
{chain: 4, addr: "0000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc3", symbol: "DAI"},
{chain: 5, addr: "0000000000000000000000008f3cf7ad23cd3cadbd9735aff958023239c6a063", symbol: "DAI"},
{chain: 6, addr: "000000000000000000000000d586e7f844cea2f87f50152665bcbc2c279d8d70", symbol: "DAI.e"},
{chain: 10, addr: "0000000000000000000000008d11ec38a3eb5e956b052f67da8bdc9bef8abf3e", symbol: "DAI"},
{chain: 13, addr: "0000000000000000000000005c74070fdea071359b86082bd9f9b3deaafbe32b", symbol: "KDAI"},
{chain: 16, addr: "000000000000000000000000765277eebeca2e31912c9946eae1021199b39c61", symbol: "DAI"},
{chain: 23, addr: "000000000000000000000000da10009cbd5d07dd0cecc66161fc93d7c9000da1", symbol: "DAI"},
{chain: 24, addr: "000000000000000000000000da10009cbd5d07dd0cecc66161fc93d7c9000da1", symbol: "DAI"},
{chain: 30, addr: "00000000000000000000000050c5725949a6f0c72e6c4a641f24049a917db0cb", symbol: "DAI"},
}
}

View File

@ -1,3 +1,5 @@
package governor
// The purpose of the Chain Governor is to limit the notional TVL that can leave a chain in a single day.
// It works by tracking transfers (types one and three) for a configured set of tokens from a configured set of emitters (chains).
//
@ -23,11 +25,10 @@
//
// To enable the chain governor, you must specified the --chainGovernorEnabled guardiand command line argument.
package governor
import (
"context"
"encoding/hex"
"errors"
"fmt"
"math"
"math/big"
@ -204,7 +205,14 @@ func (gov *ChainGovernor) initConfig() error {
}
key := tokenKey{chain: vaa.ChainID(ct.chain), addr: addr}
te := &tokenEntry{cfgPrice: cfgPrice, price: initialPrice, decimals: decimals, symbol: symbol, coinGeckoId: ct.coinGeckoId, token: key}
te := &tokenEntry{
cfgPrice: cfgPrice,
price: initialPrice,
decimals: decimals,
symbol: symbol,
coinGeckoId: ct.coinGeckoId,
token: key,
}
te.updatePrice()
gov.tokens[key] = te
@ -294,6 +302,10 @@ func (gov *ChainGovernor) ProcessMsg(msg *common.MessagePublication) bool {
}
func (gov *ChainGovernor) ProcessMsgForTime(msg *common.MessagePublication, now time.Time) (bool, error) {
// Validation:
// - ensure MessagePublication is not nil
// - check that the MessagePublication is governed
// - check that the message is not a duplicate
if msg == nil {
return false, fmt.Errorf("msg is nil")
}
@ -301,7 +313,7 @@ func (gov *ChainGovernor) ProcessMsgForTime(msg *common.MessagePublication, now
gov.mutex.Lock()
defer gov.mutex.Unlock()
msgIsGoverned, ce, token, payload, err := gov.parseMsgAlreadyLocked(msg)
msgIsGoverned, emitterChainEntry, token, payload, err := gov.parseMsgAlreadyLocked(msg)
if err != nil {
return false, err
}
@ -330,10 +342,11 @@ func (gov *ChainGovernor) ProcessMsgForTime(msg *common.MessagePublication, now
return true, nil
}
// Get all outgoing transfers for `emitterChainEntry` that happened within the last 24 hours
startTime := now.Add(-time.Minute * time.Duration(gov.dayLengthInMinutes))
prevTotalValue, err := gov.TrimAndSumValueForChain(ce, startTime)
prevTotalValue, err := gov.TrimAndSumValueForChain(emitterChainEntry, startTime)
if err != nil {
gov.logger.Error("failed to trim transfers",
gov.logger.Error("Error when attempting to trim and sum transfers",
zap.String("msgID", msg.MessageIDString()),
zap.String("hash", hash),
zap.Stringer("txHash", msg.TxHash),
@ -342,6 +355,7 @@ func (gov *ChainGovernor) ProcessMsgForTime(msg *common.MessagePublication, now
return false, err
}
// Compute the notional USD value of the transfers
value, err := computeValue(payload.Amount, token)
if err != nil {
gov.logger.Error("failed to compute value of transfer",
@ -367,7 +381,7 @@ func (gov *ChainGovernor) ProcessMsgForTime(msg *common.MessagePublication, now
enqueueIt := false
var releaseTime time.Time
if ce.isBigTransfer(value) {
if emitterChainEntry.isBigTransfer(value) {
enqueueIt = true
releaseTime = now.Add(maxEnqueuedTime)
gov.logger.Error("enqueuing vaa because it is a big transaction",
@ -376,11 +390,11 @@ func (gov *ChainGovernor) ProcessMsgForTime(msg *common.MessagePublication, now
zap.Uint64("newTotalValue", newTotalValue),
zap.String("msgID", msg.MessageIDString()),
zap.Stringer("releaseTime", releaseTime),
zap.Uint64("bigTransactionSize", ce.bigTransactionSize),
zap.Uint64("bigTransactionSize", emitterChainEntry.bigTransactionSize),
zap.String("hash", hash),
zap.Stringer("txHash", msg.TxHash),
)
} else if newTotalValue > ce.dailyLimit {
} else if newTotalValue > emitterChainEntry.dailyLimit {
enqueueIt = true
releaseTime = now.Add(maxEnqueuedTime)
gov.logger.Error("enqueuing vaa because it would exceed the daily limit",
@ -407,7 +421,10 @@ func (gov *ChainGovernor) ProcessMsgForTime(msg *common.MessagePublication, now
return false, err
}
ce.pending = append(ce.pending, &pendingEntry{token: token, amount: payload.Amount, hash: hash, dbData: dbData})
emitterChainEntry.pending = append(
emitterChainEntry.pending,
&pendingEntry{token: token, amount: payload.Amount, hash: hash, dbData: dbData},
)
gov.msgsSeen[hash] = transferEnqueued
return false, nil
}
@ -421,7 +438,8 @@ func (gov *ChainGovernor) ProcessMsgForTime(msg *common.MessagePublication, now
zap.Stringer("txHash", msg.TxHash),
)
xfer := db.Transfer{Timestamp: now,
xfer := db.Transfer{
Timestamp: now,
Value: value,
OriginChain: token.token.chain,
OriginAddress: token.token.addr,
@ -442,7 +460,7 @@ func (gov *ChainGovernor) ProcessMsgForTime(msg *common.MessagePublication, now
return false, err
}
ce.transfers = append(ce.transfers, &xfer)
emitterChainEntry.transfers = append(emitterChainEntry.transfers, &xfer)
gov.msgsSeen[hash] = transferComplete
return true, nil
}
@ -456,19 +474,27 @@ func (gov *ChainGovernor) IsGovernedMsg(msg *common.MessagePublication) (msgIsGo
}
// parseMsgAlreadyLocked determines if the message applies to the governor and also returns data useful to the governor. It assumes the caller holds the lock.
func (gov *ChainGovernor) parseMsgAlreadyLocked(msg *common.MessagePublication) (bool, *chainEntry, *tokenEntry, *vaa.TransferPayloadHdr, error) {
func (gov *ChainGovernor) parseMsgAlreadyLocked(
msg *common.MessagePublication,
) (bool, *chainEntry, *tokenEntry, *vaa.TransferPayloadHdr, error) {
// If we don't care about this chain, the VAA can be published.
ce, exists := gov.chains[msg.EmitterChain]
if !exists {
if msg.EmitterChain != vaa.ChainIDPythNet {
gov.logger.Info("ignoring vaa because the emitter chain is not configured", zap.String("msgID", msg.MessageIDString()))
gov.logger.Info(
"ignoring vaa because the emitter chain is not configured",
zap.String("msgID", msg.MessageIDString()),
)
}
return false, nil, nil, nil, nil
}
// If we don't care about this emitter, the VAA can be published.
if msg.EmitterAddress != ce.emitterAddr {
gov.logger.Info("ignoring vaa because the emitter address is not configured", zap.String("msgID", msg.MessageIDString()))
gov.logger.Info(
"ignoring vaa because the emitter address is not configured",
zap.String("msgID", msg.MessageIDString()),
)
return false, nil, nil, nil, nil
}
@ -519,7 +545,7 @@ func (gov *ChainGovernor) CheckPendingForTime(now time.Time) ([]*common.MessageP
foundOne := false
prevTotalValue, err := gov.TrimAndSumValueForChain(ce, startTime)
if err != nil {
gov.logger.Error("failed to trim transfers", zap.Error(err))
gov.logger.Error("Error when attempting to trim and sum transfers", zap.Error(err))
gov.msgsToPublish = msgsToPublish
return nil, err
}
@ -656,11 +682,110 @@ func computeValue(amount *big.Int, token *tokenEntry) (uint64, error) {
return value, nil
}
func (gov *ChainGovernor) TrimAndSumValueForChain(ce *chainEntry, startTime time.Time) (sum uint64, err error) {
sum, ce.transfers, err = gov.TrimAndSumValue(ce.transfers, startTime)
return sum, err
// TrimAndSumValueForChain calculates the `sum` of `Transfer`s for a given chain `emitter`. In effect, it represents a
// chain's "Governor Usage" for a given 24 hour period.
// This sum may be reduced by the sum of 'flow cancelling' transfers: that is, transfers of an allow-listed token
// that have the `emitter` as their destination chain.
// The resulting `sum` return value therefore represents the net flow across a chain when taking flow-cancelling tokens
// into account. Therefore, this value should never be less than 0 and should never exceed the "Governor limit" for the chain.
//
// As a side-effect, this function modifies the parameter `emitter`, upating its `transfers` field so that it only includes
// filtered `Transfer`s (i.e. outgoing `Transfer`s newer than `startTime`).
//
// SECURITY Invariant: The `sum` return value should never be less than 0
// SECURITY Invariant: The `sum` return value should never exceed the "Governor limit" for the chain
func (gov *ChainGovernor) TrimAndSumValueForChain(emitter *chainEntry, startTime time.Time) (sum uint64, err error) {
// Sum the value of all outgoing transfers
var sumOutgoing uint64
sumOutgoing, emitter.transfers, err = gov.TrimAndSumValue(emitter.transfers, startTime)
if err != nil {
return 0, err
}
// Subtract the sum of all flow cancelling transfers. Here the emitter's chainID is actually used as the destination
// chain in in the context of FlowCancellingTransfersForChain.
flowCancelSum, err := gov.SumTransferValues(gov.FlowCancellingTransfersForChain(emitter.emitterChainId, startTime))
if err != nil {
return 0, err
}
// If flowCancelSum is larger than or equal to sumOutgoing, simply return 0 as this means that all outgoing
// transfers have been cancelled by incoming transfers.
// This also avoids integer underflow.
if flowCancelSum >= sumOutgoing {
return 0, nil
}
sum = sumOutgoing - flowCancelSum
if sum > emitter.dailyLimit {
return 0, fmt.Errorf(
"invariant violation: calculated sum %d exceeds Governor limit %d",
sum,
emitter.dailyLimit,
)
}
return sum, nil
}
// SumTransferValues iterates over a slice of transfers and sums their value.
func (gov *ChainGovernor) SumTransferValues(transfers []*db.Transfer) (sum uint64, err error) {
sum = 0
// Iterate over all transfers usin tokens that have flow cancelling enabled.
// If the destination chain of the transfer is equal to the `ce` parameter, add the value
// of the transfer to the flow cancelling sum.
for _, transfer := range transfers {
// Overflow check. Note that transfer.Value cannot be negative
if (sum + transfer.Value) < sum {
return 0, errors.New("overflow when calculating flow cancelling sum")
}
sum += transfer.Value
}
return sum, nil
}
// FlowCancellingTransfersForChain builds a list of Transfers that contain assets that can 'flow cancel'
// (reduce the Governor usage of) a governed chain represented by `destinationChainID`.
func (gov *ChainGovernor) FlowCancellingTransfersForChain(
destinationChainID vaa.ChainID,
startTime time.Time,
) (transfers []*db.Transfer) {
flowCancelTokens := FlowCancelTokenList()
// transfers = make([]*db.Transfer, 0)
// Iterate over all transfers for all governed chains
for _, emitterChainEntry := range gov.chains {
for _, transfer := range emitterChainEntry.transfers {
// We care about the transfer if:
// - Its target chain is equal to the `destinationChainID` parameter
// - It happened after `startTime`
// - It is a flow cancelling token (The transfer's origin chain and
// origin address match a hard-coded flow cancelling asset)
if transfer.TargetChain != destinationChainID {
continue
}
if transfer.Timestamp.Before(startTime) {
continue
}
for _, flowCancelToken := range flowCancelTokens {
// Compare flow cancel fields with transfer fields. This requires conversions:
// - vaa.ChainID to uint16
// - vaa.Address to String
if uint16(transfer.OriginChain) == flowCancelToken.chain &&
transfer.OriginAddress.String() == flowCancelToken.addr {
transfers = append(transfers, transfer)
}
}
}
}
return transfers
}
// TrimAndSumValue iterates over a slice of db.Transfer structs. It filters out transfers that have a Timestamp value that
// is earlier than the parameter `startTime`. The function then iterates over the remaining transfers, sums their Value,
// and returns the sum and the filtered transfers.
func (gov *ChainGovernor) TrimAndSumValue(transfers []*db.Transfer, startTime time.Time) (uint64, []*db.Transfer, error) {
if len(transfers) == 0 {
return 0, transfers, nil

View File

@ -49,7 +49,12 @@ func (gov *ChainGovernor) setDayLengthInMinutes(min int) {
gov.dayLengthInMinutes = min
}
func (gov *ChainGovernor) setChainForTesting(emitterChainId vaa.ChainID, emitterAddrStr string, dailyLimit uint64, bigTransactionSize uint64) error {
func (gov *ChainGovernor) setChainForTesting(
emitterChainId vaa.ChainID,
emitterAddrStr string,
dailyLimit uint64,
bigTransactionSize uint64,
) error {
gov.mutex.Lock()
defer gov.mutex.Unlock()
@ -70,7 +75,12 @@ func (gov *ChainGovernor) setChainForTesting(emitterChainId vaa.ChainID, emitter
return nil
}
func (gov *ChainGovernor) setTokenForTesting(tokenChainID vaa.ChainID, tokenAddrStr string, symbol string, price float64) error {
func (gov *ChainGovernor) setTokenForTesting(
tokenChainID vaa.ChainID,
tokenAddrStr string,
symbol string,
price float64,
) error {
gov.mutex.Lock()
defer gov.mutex.Unlock()
@ -163,6 +173,214 @@ func TestSumAllFromToday(t *testing.T) {
assert.Equal(t, 1, len(updatedTransfers))
}
func TestSumWithFlowCancelling(t *testing.T) {
// NOTE: Replace this Chain:Address pair if the Flow Cancel Token List is modified
var originChain vaa.ChainID = 2
var originAddress vaa.Address
originAddress, err := vaa.StringToAddress("000000000000000000000000bcca60bb61934080951369a648fb03df4f96263c")
require.NoError(t, err)
ctx := context.Background()
gov, err := newChainGovernorForTest(ctx)
require.NoError(t, err)
assert.NotNil(t, gov)
now, err := time.Parse("2006-Jan-02", "2024-Feb-19")
require.NoError(t, err)
var transfers_from_emitter []*db.Transfer
var transfers_that_flow_cancel []*db.Transfer
transferTime, err := time.Parse("2006-Jan-02", "2024-Feb-19")
require.NoError(t, err)
// Set up values and governor limit
emitterTransferValue := uint64(125000)
flowCancelValue := uint64(100000)
emitterLimit := emitterTransferValue * 2 // make sure the limit always exceeds the transfer value
emitterChainId := 1
// Setup transfers
// - Transfer from emitter: we only care about Value
// - Transfer that flow cancels: Transfer must be a valid entry from FlowCancelTokenList() (based on origin chain and origin address)
// and the desintation chain must be the same as the emitter chain
transfers_from_emitter = append(transfers_from_emitter, &db.Transfer{Value: emitterTransferValue, Timestamp: transferTime})
transfers_that_flow_cancel = append(
transfers_that_flow_cancel,
&db.Transfer{
OriginChain: originChain,
OriginAddress: originAddress,
TargetChain: vaa.ChainID(emitterChainId),
Value: flowCancelValue,
Timestamp: transferTime,
},
)
// Populate chainEntrys and ChainGovernor
emitter := &chainEntry{
transfers: transfers_from_emitter,
emitterChainId: vaa.ChainID(emitterChainId),
dailyLimit: emitterLimit,
}
chain_with_flow_cancel_transfers := &chainEntry{transfers: transfers_that_flow_cancel, emitterChainId: 2}
gov.chains[emitter.emitterChainId] = emitter
gov.chains[chain_with_flow_cancel_transfers.emitterChainId] = chain_with_flow_cancel_transfers
// XXX: sanity check
expectedNumTransfers := 1
sum, transfers, err := gov.TrimAndSumValue(emitter.transfers, now)
require.NoError(t, err)
assert.Equal(t, expectedNumTransfers, len(transfers))
assert.NotZero(t, sum)
// Calculate Governor Usage for emitter, including flow cancelling
sum, err = gov.TrimAndSumValueForChain(emitter, now.Add(-time.Hour*24))
require.NoError(t, err)
assert.Equal(t, emitterTransferValue-flowCancelValue, sum)
}
// Flow cancelling transfers are subtracted from the overall sum of all transfers from a given
// emitter chain. Since we are working with uint64 values, ensure that there is no underflow.
// When the sum of all flow cancelling transfers is greater than emitted transfers for a chain,
// the expected result is that the resulting Governor Usage equals 0 (and not a negative number
// or a very large underflow result).
// Also, the function should not return an error in this case.
func TestFlowCancelCannotUnderflow(t *testing.T) {
// NOTE: Replace this Chain:Address pair if the Flow Cancel Token List is modified
var originChain vaa.ChainID = 2
var originAddress vaa.Address
originAddress, err := vaa.StringToAddress("000000000000000000000000bcca60bb61934080951369a648fb03df4f96263c")
require.NoError(t, err)
ctx := context.Background()
gov, err := newChainGovernorForTest(ctx)
require.NoError(t, err)
assert.NotNil(t, gov)
now, err := time.Parse("2006-Jan-02", "2024-Feb-19")
require.NoError(t, err)
var transfers_from_emitter []*db.Transfer
var transfers_that_flow_cancel []*db.Transfer
transferTime, err := time.Parse("2006-Jan-02", "2024-Feb-19")
require.NoError(t, err)
// Set up values and governor limit
emitterTransferValue := uint64(100000)
flowCancelValue := emitterTransferValue + 25000 // make sure this value is higher than `emitterTransferValue`
emitterLimit := emitterTransferValue * 2 // make sure the limit always exceeds the transfer value
emitterChainId := 1
// Setup transfers
// - Transfer from emitter: we only care about Value
// - Transfer that flow cancels: Transfer must be a valid entry from FlowCancelTokenList() (based on origin chain and origin address)
// and the destination chain must be the same as the emitter chain
transfers_from_emitter = append(transfers_from_emitter, &db.Transfer{Value: emitterTransferValue, Timestamp: transferTime})
transfers_that_flow_cancel = append(
transfers_that_flow_cancel,
&db.Transfer{
OriginChain: originChain,
OriginAddress: originAddress,
TargetChain: vaa.ChainID(emitterChainId),
Value: flowCancelValue,
Timestamp: transferTime,
},
)
// Populate chainEntrys and ChainGovernor
emitter := &chainEntry{
transfers: transfers_from_emitter,
emitterChainId: vaa.ChainID(emitterChainId),
dailyLimit: emitterLimit,
}
chain_with_flow_cancel_transfers := &chainEntry{transfers: transfers_that_flow_cancel, emitterChainId: 2}
gov.chains[emitter.emitterChainId] = emitter
gov.chains[chain_with_flow_cancel_transfers.emitterChainId] = chain_with_flow_cancel_transfers
// XXX: sanity check: Sum of transfers without flow cancelling should be positive.
expectedNumTransfers := 1
sum, transfers, err := gov.TrimAndSumValue(emitter.transfers, now)
require.NoError(t, err)
assert.Equal(t, expectedNumTransfers, len(transfers))
assert.Greater(t, sum, uint64(0))
// Calculate Governor Usage for emitter, including flow cancelling
sum, err = gov.TrimAndSumValueForChain(emitter, now.Add(-time.Hour*24))
require.NoError(t, err)
assert.Zero(t, sum)
}
// Simulate a case where the total sum of transfers for a chain in a 24 hour period exceeds
// the configured Governor limit. This should never happen, so we make sure that an error
// is returned if the system is in this state
func TestInvariantGovernorLimit(t *testing.T) {
ctx := context.Background()
gov, err := newChainGovernorForTest(ctx)
require.NoError(t, err)
assert.NotNil(t, gov)
now, err := time.Parse("2006-Jan-02", "2024-Feb-19")
require.NoError(t, err)
var transfers_from_emitter []*db.Transfer
transferTime, err := time.Parse("2006-Jan-02", "2024-Feb-19")
require.NoError(t, err)
emitterTransferValue := uint64(125000)
emitterLimit := emitterTransferValue * 20
emitterChainId := 1
// Create a lot of transfers. Their total value should exceed `emitterLimit`
for i := 0; i < 25; i++ {
transfers_from_emitter = append(
transfers_from_emitter,
&db.Transfer{Value: emitterTransferValue, Timestamp: transferTime},
)
}
// Populate chainEntry and ChainGovernor
emitter := &chainEntry{
transfers: transfers_from_emitter,
emitterChainId: vaa.ChainID(emitterChainId),
dailyLimit: emitterLimit,
}
gov.chains[emitter.emitterChainId] = emitter
// XXX: sanity check
expectedNumTransfers := 25
sum, transfers, err := gov.TrimAndSumValue(emitter.transfers, now)
require.NoError(t, err)
assert.Equal(t, expectedNumTransfers, len(transfers))
assert.NotZero(t, sum)
// Make sure we trigger the Invariant
sum, err = gov.TrimAndSumValueForChain(emitter, now.Add(-time.Hour*24))
require.ErrorContains(t, err, "invariant violation: calculated sum")
assert.Zero(t, sum)
}
func TestInvariantSumOverflow(t *testing.T) {
ctx := context.Background()
gov, err := newChainGovernorForTest(ctx)
require.NoError(t, err)
assert.NotNil(t, gov)
transferTime, err := time.Parse("2006-Jan-02", "2024-Feb-19")
require.NoError(t, err)
var transfers []*db.Transfer
// Add two transfers. When summed, they should trigger an overflow
transfers = append(transfers, &db.Transfer{Value: math.MaxUint64, Timestamp: transferTime})
transfers = append(transfers, &db.Transfer{Value: 1, Timestamp: transferTime})
sum, err := gov.SumTransferValues(transfers)
require.ErrorContains(t, err, "overflow when calculating flow cancelling sum")
assert.Zero(t, sum)
}
func TestTrimOneOfTwoTransfers(t *testing.T) {
ctx := context.Background()
gov, err := newChainGovernorForTest(ctx)
@ -316,7 +534,7 @@ func TestVaaForUninterestingEmitterChain(t *testing.T) {
assert.NotNil(t, gov)
emitterAddr, _ := vaa.StringToAddress("0x00")
var payload = []byte{1, 97, 97, 97, 97, 97}
payload := []byte{1, 97, 97, 97, 97, 97}
msg := common.MessagePublication{
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
@ -348,7 +566,7 @@ func TestVaaForUninterestingEmitterAddress(t *testing.T) {
assert.NotNil(t, gov)
emitterAddr, _ := vaa.StringToAddress("0x00")
var payload = []byte{1, 97, 97, 97, 97, 97}
payload := []byte{1, 97, 97, 97, 97, 97}
msg := common.MessagePublication{
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
@ -381,7 +599,7 @@ func TestVaaForUninterestingPayloadType(t *testing.T) {
assert.NotNil(t, gov)
emitterAddr, _ := vaa.StringToAddress("0x0290fb167208af455bb137780163b7b7a9a10c16")
var payload = []byte{2, 97, 97, 97, 97, 97}
payload := []byte{2, 97, 97, 97, 97, 97}
msg := common.MessagePublication{
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),