3421 lines
128 KiB
Go
3421 lines
128 KiB
Go
//nolint:unparam // we exclude the unparam linter because there are many cases here where parameters are static
|
|
package governor
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
eth_common "github.com/ethereum/go-ethereum/common"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/certusone/wormhole/node/pkg/common"
|
|
"github.com/certusone/wormhole/node/pkg/db"
|
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
"go.uber.org/zap/zaptest"
|
|
"go.uber.org/zap/zaptest/observer"
|
|
)
|
|
|
|
// This is so we can have consistent config data for unit tests.
|
|
func (gov *ChainGovernor) initConfigForTest(
|
|
emitterChainID vaa.ChainID,
|
|
emitterAddr vaa.Address,
|
|
dailyLimit uint64,
|
|
tokenChainID vaa.ChainID,
|
|
tokenAddr vaa.Address,
|
|
tokenSymbol string,
|
|
tokenPrice float64,
|
|
tokenDecimals int64,
|
|
) {
|
|
gov.chains[emitterChainID] = &chainEntry{emitterChainId: emitterChainID, emitterAddr: emitterAddr, dailyLimit: dailyLimit}
|
|
|
|
price := big.NewFloat(tokenPrice)
|
|
decimalsFloat := big.NewFloat(math.Pow(10.0, float64(tokenDecimals)))
|
|
decimals, _ := decimalsFloat.Int(nil)
|
|
key := tokenKey{chain: tokenChainID, addr: tokenAddr}
|
|
|
|
gov.tokens[key] = &tokenEntry{price: price, decimals: decimals, symbol: tokenSymbol, token: key}
|
|
}
|
|
|
|
func (gov *ChainGovernor) setDayLengthInMinutes(min int) {
|
|
gov.dayLengthInMinutes = min
|
|
}
|
|
|
|
// Utility method: adds a new `chainEntry` to `gov`
|
|
// Supplying a bigTransactionSize of 0 will skip checks for big transactions.
|
|
func (gov *ChainGovernor) setChainForTesting(
|
|
emitterChainId vaa.ChainID,
|
|
emitterAddrStr string,
|
|
dailyLimit uint64,
|
|
bigTransactionSize uint64,
|
|
) error {
|
|
gov.mutex.Lock()
|
|
defer gov.mutex.Unlock()
|
|
|
|
emitterAddr, err := vaa.StringToAddress(emitterAddrStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ce := &chainEntry{
|
|
emitterChainId: emitterChainId,
|
|
emitterAddr: emitterAddr,
|
|
dailyLimit: dailyLimit,
|
|
bigTransactionSize: bigTransactionSize,
|
|
checkForBigTransactions: bigTransactionSize != 0,
|
|
}
|
|
|
|
gov.chains[emitterChainId] = ce
|
|
return nil
|
|
}
|
|
|
|
// Utility method: adds a new `tokenEntry` to `gov`
|
|
func (gov *ChainGovernor) setTokenForTesting(
|
|
tokenChainID vaa.ChainID,
|
|
tokenAddrStr string,
|
|
symbol string,
|
|
price float64,
|
|
flowCancels bool,
|
|
) error {
|
|
gov.mutex.Lock()
|
|
defer gov.mutex.Unlock()
|
|
|
|
tokenAddr, err := vaa.StringToAddress(tokenAddrStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bigPrice := big.NewFloat(price)
|
|
decimalsFloat := big.NewFloat(math.Pow(10.0, float64(8)))
|
|
decimals, _ := decimalsFloat.Int(nil)
|
|
|
|
key := tokenKey{chain: tokenChainID, addr: tokenAddr}
|
|
te := &tokenEntry{cfgPrice: bigPrice, price: bigPrice, decimals: decimals, symbol: symbol, coinGeckoId: symbol, token: key, flowCancels: flowCancels}
|
|
gov.tokens[key] = te
|
|
cge, cgExists := gov.tokensByCoinGeckoId[te.coinGeckoId]
|
|
if !cgExists {
|
|
gov.tokensByCoinGeckoId[te.coinGeckoId] = []*tokenEntry{te}
|
|
} else {
|
|
cge = append(cge, te)
|
|
gov.tokensByCoinGeckoId[te.coinGeckoId] = cge
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getStatsForAllChains sums the number of transfers, value of all transfers, number of pending transfers,
|
|
// and the value of the pending transfers.
|
|
// Note that 'flow cancel transfers' are not included and therefore the values returned by this function may not
|
|
// match the Governor usage.
|
|
func (gov *ChainGovernor) getStatsForAllChains() (numTrans int, valueTrans uint64, numPending int, valuePending uint64) {
|
|
gov.mutex.Lock()
|
|
defer gov.mutex.Unlock()
|
|
|
|
for _, ce := range gov.chains {
|
|
numTrans += len(ce.transfers)
|
|
for _, te := range ce.transfers {
|
|
valueTrans += te.dbTransfer.Value
|
|
}
|
|
|
|
numPending += len(ce.pending)
|
|
for _, pe := range ce.pending {
|
|
value, _ := computeValue(pe.amount, pe.token)
|
|
valuePending += value
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// getStatsForAllChains but includes flow cancelling in its statistics. This results in different values for valueTrans
|
|
// TODO these functions can probably be merged together and a boolean can be passed if we want flow cancel results.
|
|
func (gov *ChainGovernor) getStatsForAllChainsCancelFlow() (numTrans int, valueTrans int64, numPending int, valuePending uint64) {
|
|
gov.mutex.Lock()
|
|
defer gov.mutex.Unlock()
|
|
|
|
for _, ce := range gov.chains {
|
|
numTrans += len(ce.transfers)
|
|
for _, te := range ce.transfers {
|
|
valueTrans += te.value // Needs to be .value and not .dbTransfer.value because we want the SIGNED version of this.
|
|
}
|
|
|
|
numPending += len(ce.pending)
|
|
for _, pe := range ce.pending {
|
|
value, _ := computeValue(pe.amount, pe.token)
|
|
valuePending += value
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func checkTargetOnReleasedIsSet(t *testing.T, toBePublished []*common.MessagePublication, targetChain vaa.ChainID, targetAddressStr string) {
|
|
require.NotEqual(t, 0, len(toBePublished))
|
|
toAddr, err := vaa.StringToAddress(targetAddressStr)
|
|
require.NoError(t, err)
|
|
for _, msg := range toBePublished {
|
|
payload, err := vaa.DecodeTransferPayloadHdr(msg.Payload)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, targetChain, payload.TargetChain)
|
|
assert.Equal(t, toAddr, payload.TargetAddress)
|
|
}
|
|
}
|
|
|
|
func TestTrimEmptyTransfers(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
now, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
require.NoError(t, err)
|
|
|
|
var transfers []transfer
|
|
sum, updatedTransfers, err := gov.TrimAndSumValue(transfers, now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), sum)
|
|
assert.Equal(t, 0, len(updatedTransfers))
|
|
}
|
|
|
|
// Make sure that the code doesn't panic if called with a nil chainEntry
|
|
func TestTrimAndSumValueForChainReturnsErrorForNilChainEntry(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
now, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
require.NoError(t, err)
|
|
|
|
sum, err := gov.TrimAndSumValueForChain(nil, now)
|
|
require.Error(t, err)
|
|
assert.Equal(t, uint64(0), sum)
|
|
}
|
|
|
|
func TestSumAllFromToday(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
now, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
require.NoError(t, err)
|
|
|
|
var transfers []transfer
|
|
transferTime, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 11:00am (CST)")
|
|
require.NoError(t, err)
|
|
dbTransfer := &db.Transfer{Value: 125000, Timestamp: transferTime}
|
|
transfer, err := newTransferFromDbTransfer(dbTransfer)
|
|
require.NoError(t, err)
|
|
transfers = append(transfers, transfer)
|
|
sum, updatedTransfers, err := gov.TrimAndSumValue(transfers, now.Add(-time.Hour*24))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint64(125000), uint64(sum))
|
|
assert.Equal(t, 1, len(updatedTransfers))
|
|
}
|
|
|
|
// Checks sum calculation for the flow cancel mechanism
|
|
func TestSumWithFlowCancelling(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
// Choose a hard-coded value from the Flow Cancel Token List
|
|
// NOTE: Replace this Chain:Address pair if the Flow Cancel Token List is modified
|
|
var originChain vaa.ChainID = 1
|
|
var originAddress vaa.Address
|
|
originAddress, err = vaa.StringToAddress("c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61")
|
|
require.NoError(t, err)
|
|
|
|
// Ensure asset is registered in the governor and can flow cancel
|
|
key := tokenKey{originChain, originAddress}
|
|
assert.True(t, gov.tokens[key].flowCancels)
|
|
|
|
now, err := time.Parse("2006-Jan-02", "2024-Feb-19")
|
|
require.NoError(t, err)
|
|
|
|
var chainEntryTransfers []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 destination chain must be the same as the emitter chain
|
|
outgoingDbTransfer := &db.Transfer{Value: emitterTransferValue, Timestamp: transferTime}
|
|
outgoingTransfer, err := newTransferFromDbTransfer(outgoingDbTransfer)
|
|
require.NoError(t, err)
|
|
|
|
// Flow cancelling transfer
|
|
incomingDbTransfer := &db.Transfer{
|
|
OriginChain: originChain,
|
|
OriginAddress: originAddress,
|
|
TargetChain: vaa.ChainID(emitterChainId), // emitter
|
|
Value: flowCancelValue,
|
|
Timestamp: transferTime,
|
|
}
|
|
|
|
chainEntryTransfers = append(chainEntryTransfers, outgoingTransfer)
|
|
|
|
// Populate chainEntry and ChainGovernor
|
|
emitter := &chainEntry{
|
|
transfers: chainEntryTransfers,
|
|
emitterChainId: vaa.ChainID(emitterChainId),
|
|
dailyLimit: emitterLimit,
|
|
}
|
|
|
|
err = emitter.addFlowCancelTransferFromDbTransfer(incomingDbTransfer)
|
|
require.NoError(t, err)
|
|
|
|
gov.chains[emitter.emitterChainId] = emitter
|
|
|
|
// Sanity check: ensure that there are transfers in the chainEntry
|
|
expectedNumTransfers := 2
|
|
_, transfers, err := gov.TrimAndSumValue(emitter.transfers, now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expectedNumTransfers, len(transfers))
|
|
|
|
// Calculate Governor Usage for emitter, including flow cancelling.
|
|
usage, err := gov.TrimAndSumValueForChain(emitter, now.Add(-time.Hour*24))
|
|
require.NoError(t, err)
|
|
difference := uint64(25000) // emitterTransferValue - flowCancelTransferValue
|
|
assert.Equal(t, difference, usage)
|
|
}
|
|
|
|
func TestFlowCancelFeatureFlag(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
var db db.MockGovernorDB
|
|
gov := NewChainGovernor(zap.NewNop(), &db, common.GoTest, true)
|
|
|
|
// Trigger the evaluation of the flow cancelling config
|
|
err := gov.Run(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
// Test private bool
|
|
assert.True(t, gov.flowCancelEnabled)
|
|
// Test public getter
|
|
assert.True(t, gov.IsFlowCancelEnabled())
|
|
numFlowCancelling := 0
|
|
for _, tokenEntry := range gov.tokens {
|
|
if tokenEntry.flowCancels == true {
|
|
numFlowCancelling++
|
|
}
|
|
}
|
|
assert.NotZero(t, numFlowCancelling)
|
|
|
|
// Disable flow cancelling
|
|
gov = NewChainGovernor(zap.NewNop(), &db, common.GoTest, false)
|
|
|
|
// Trigger the evaluation of the flow cancelling config
|
|
err = gov.Run(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
// Test private bool
|
|
assert.False(t, gov.flowCancelEnabled)
|
|
// Test public getter
|
|
assert.False(t, gov.IsFlowCancelEnabled())
|
|
numFlowCancelling = 0
|
|
for _, tokenEntry := range gov.tokens {
|
|
if tokenEntry.flowCancels == true {
|
|
numFlowCancelling++
|
|
}
|
|
}
|
|
assert.Zero(t, numFlowCancelling)
|
|
|
|
}
|
|
|
|
// 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) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
// Set-up asset to be used in the test
|
|
// NOTE: Replace this Chain:Address pair if the Flow Cancel Token List is modified
|
|
var originChain vaa.ChainID = 1
|
|
var originAddress vaa.Address
|
|
originAddress, err = vaa.StringToAddress("c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61")
|
|
require.NoError(t, err)
|
|
|
|
// Ensure asset is registered in the governor and can flow cancel
|
|
key := tokenKey{originChain, originAddress}
|
|
assert.True(t, gov.tokens[key].flowCancels)
|
|
|
|
now, err := time.Parse("2006-Jan-02", "2024-Feb-19")
|
|
require.NoError(t, err)
|
|
|
|
var transfers_from_emitter []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
|
|
emitterDbTransfer := &db.Transfer{Value: emitterTransferValue, Timestamp: transferTime}
|
|
emitterTransfer, err := newTransferFromDbTransfer(emitterDbTransfer)
|
|
require.NoError(t, err)
|
|
transfers_from_emitter = append(transfers_from_emitter, emitterTransfer)
|
|
|
|
flowCancelDbTransfer := &db.Transfer{
|
|
OriginChain: originChain,
|
|
OriginAddress: originAddress,
|
|
TargetChain: vaa.ChainID(emitterChainId), // emitter
|
|
Value: flowCancelValue,
|
|
Timestamp: transferTime,
|
|
}
|
|
|
|
// Populate chainEntrys and ChainGovernor
|
|
emitter := &chainEntry{
|
|
transfers: transfers_from_emitter,
|
|
emitterChainId: vaa.ChainID(emitterChainId),
|
|
dailyLimit: emitterLimit,
|
|
}
|
|
err = emitter.addFlowCancelTransferFromDbTransfer(flowCancelDbTransfer)
|
|
require.NoError(t, err)
|
|
|
|
gov.chains[emitter.emitterChainId] = emitter
|
|
|
|
expectedNumTransfers := 2
|
|
_, transfers, err := gov.TrimAndSumValue(emitter.transfers, now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expectedNumTransfers, len(transfers))
|
|
|
|
// Calculate Governor Usage for emitter, including flow cancelling
|
|
// Should be zero when flow cancel transfer values exceed emitted transfer values.
|
|
usage, err := gov.TrimAndSumValueForChain(emitter, now.Add(-time.Hour*24))
|
|
require.NoError(t, err)
|
|
assert.Zero(t, usage)
|
|
}
|
|
|
|
// We never expect this to occur when flow-cancelling is disabled. If flow-cancelling is enabled, there
|
|
// are some cases where the outgoing value exceeds the daily limit. Example: a large, incoming transfer
|
|
// of a flow-cancelling asset increases the Governor capacity beyond the daily limit. After 24h, that
|
|
// transfer is trimmed. This reduces the daily limit back to normal, but by this time more outgoing
|
|
// transfers have been emitted, causing the sum to exceed the daily limit.
|
|
func TestChainEntrySumExceedsDailyLimit(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 []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++ {
|
|
transfer, err := newTransferFromDbTransfer(&db.Transfer{Value: emitterTransferValue, Timestamp: transferTime})
|
|
require.NoError(t, err)
|
|
transfers_from_emitter = append(
|
|
transfers_from_emitter,
|
|
transfer,
|
|
)
|
|
}
|
|
|
|
// 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)
|
|
|
|
usage, err := gov.TrimAndSumValueForChain(emitter, now.Add(-time.Hour*24))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, emitterTransferValue*uint64(expectedNumTransfers), usage)
|
|
}
|
|
|
|
func TestTrimAndSumValueOverflowErrors(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 []transfer
|
|
transferTime, err := time.Parse("2006-Jan-02", "2024-Feb-19")
|
|
require.NoError(t, err)
|
|
|
|
emitterChainId := vaa.ChainIDSolana
|
|
|
|
transfer, err := newTransferFromDbTransfer(&db.Transfer{Value: math.MaxInt64, Timestamp: transferTime})
|
|
require.NoError(t, err)
|
|
transfer2, err := newTransferFromDbTransfer(&db.Transfer{Value: 1, Timestamp: transferTime})
|
|
require.NoError(t, err)
|
|
transfers_from_emitter = append(transfers_from_emitter, transfer, transfer2)
|
|
|
|
// Populate chainEntry and ChainGovernor
|
|
emitter := &chainEntry{
|
|
transfers: transfers_from_emitter,
|
|
emitterChainId: vaa.ChainID(emitterChainId),
|
|
dailyLimit: 10000,
|
|
}
|
|
gov.chains[emitter.emitterChainId] = emitter
|
|
|
|
sum, _, err := gov.TrimAndSumValue(emitter.transfers, now.Add(-time.Hour*24))
|
|
require.ErrorContains(t, err, "integer overflow")
|
|
assert.Zero(t, sum)
|
|
usage, err := gov.TrimAndSumValueForChain(emitter, now.Add(-time.Hour*24))
|
|
require.ErrorContains(t, err, "integer overflow")
|
|
assert.Equal(t, uint64(10000), usage)
|
|
|
|
// overwrite emitter (discard transfer added above)
|
|
emitter = &chainEntry{
|
|
emitterChainId: vaa.ChainID(emitterChainId),
|
|
dailyLimit: 10000,
|
|
}
|
|
gov.chains[emitter.emitterChainId] = emitter
|
|
|
|
// Now test underflow
|
|
transfer3 := &db.Transfer{Value: math.MaxInt64, Timestamp: transferTime, TargetChain: vaa.ChainIDSolana}
|
|
|
|
ce := gov.chains[emitter.emitterChainId]
|
|
err = ce.addFlowCancelTransferFromDbTransfer(transfer3)
|
|
require.NoError(t, err)
|
|
err = ce.addFlowCancelTransferFromDbTransfer(transfer3)
|
|
require.NoError(t, err)
|
|
|
|
sum, _, err = gov.TrimAndSumValue(emitter.transfers, now.Add(-time.Hour*24))
|
|
require.ErrorContains(t, err, "integer underflow")
|
|
assert.Zero(t, sum)
|
|
usage, err = gov.TrimAndSumValueForChain(emitter, now.Add(-time.Hour*24))
|
|
require.ErrorContains(t, err, "integer underflow")
|
|
assert.Equal(t, uint64(10000), usage)
|
|
}
|
|
|
|
func TestTrimOneOfTwoTransfers(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
now, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
require.NoError(t, err)
|
|
|
|
var transfers []transfer
|
|
|
|
// The first transfer should be expired.
|
|
transferTime1, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "May 31, 2022 at 11:59am (CST)")
|
|
require.NoError(t, err)
|
|
dbTransfer := &db.Transfer{Value: 125000, Timestamp: transferTime1}
|
|
transfer, err := newTransferFromDbTransfer(dbTransfer)
|
|
require.NoError(t, err)
|
|
transfers = append(transfers, transfer)
|
|
|
|
// But the second should not.
|
|
transferTime2, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "May 31, 2022 at 1:00pm (CST)")
|
|
require.NoError(t, err)
|
|
dbTransfer = &db.Transfer{Value: 225000, Timestamp: transferTime2}
|
|
transfer2, err := newTransferFromDbTransfer(dbTransfer)
|
|
require.NoError(t, err)
|
|
transfers = append(transfers, transfer2)
|
|
assert.Equal(t, 2, len(transfers))
|
|
|
|
sum, updatedTransfers, err := gov.TrimAndSumValue(transfers, now.Add(-time.Hour*24))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(updatedTransfers))
|
|
assert.Equal(t, uint64(225000), uint64(sum))
|
|
}
|
|
|
|
func TestTrimSeveralTransfers(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
now, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
require.NoError(t, err)
|
|
|
|
var transfers []transfer
|
|
|
|
// The first two transfers should be expired.
|
|
transferTime1, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "May 31, 2022 at 10:00am (CST)")
|
|
require.NoError(t, err)
|
|
dbTransfer1 := &db.Transfer{Value: 125000, Timestamp: transferTime1}
|
|
transfer1, err := newTransferFromDbTransfer(dbTransfer1)
|
|
require.NoError(t, err)
|
|
transfers = append(transfers, transfer1)
|
|
|
|
transferTime2, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "May 31, 2022 at 11:00am (CST)")
|
|
require.NoError(t, err)
|
|
dbTransfer2 := &db.Transfer{Value: 135000, Timestamp: transferTime2}
|
|
transfer2, err := newTransferFromDbTransfer(dbTransfer2)
|
|
require.NoError(t, err)
|
|
transfers = append(transfers, transfer2)
|
|
|
|
// But the next three should not.
|
|
transferTime3, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "May 31, 2022 at 1:00pm (CST)")
|
|
require.NoError(t, err)
|
|
dbTransfer3 := &db.Transfer{Value: 145000, Timestamp: transferTime3}
|
|
transfer3, err := newTransferFromDbTransfer(dbTransfer3)
|
|
require.NoError(t, err)
|
|
transfers = append(transfers, transfer3)
|
|
|
|
transferTime4, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "May 31, 2022 at 2:00pm (CST)")
|
|
require.NoError(t, err)
|
|
dbTransfer4 := &db.Transfer{Value: 155000, Timestamp: transferTime4}
|
|
transfer4, err := newTransferFromDbTransfer(dbTransfer4)
|
|
require.NoError(t, err)
|
|
transfers = append(transfers, transfer4)
|
|
|
|
transferTime5, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "May 31, 2022 at 2:00pm (CST)")
|
|
require.NoError(t, err)
|
|
dbTransfer5 := &db.Transfer{Value: 165000, Timestamp: transferTime5}
|
|
transfer5, err := newTransferFromDbTransfer(dbTransfer5)
|
|
require.NoError(t, err)
|
|
transfers = append(transfers, transfer5)
|
|
|
|
assert.Equal(t, 5, len(transfers))
|
|
|
|
sum, updatedTransfers, err := gov.TrimAndSumValue(transfers, now.Add(-time.Hour*24))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3, len(updatedTransfers))
|
|
assert.Equal(t, uint64(465000), uint64(sum))
|
|
}
|
|
|
|
func TestTrimmingAllTransfersShouldReturnZero(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
now, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
require.NoError(t, err)
|
|
|
|
var transfers []transfer
|
|
|
|
transferTime1, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "May 31, 2022 at 11:00am (CST)")
|
|
require.NoError(t, err)
|
|
dbTransfer1 := &db.Transfer{Value: 125000, Timestamp: transferTime1}
|
|
transfer1, err := newTransferFromDbTransfer(dbTransfer1)
|
|
require.NoError(t, err)
|
|
transfers = append(transfers, transfer1)
|
|
|
|
transferTime2, err := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "May 31, 2022 at 11:45am (CST)")
|
|
require.NoError(t, err)
|
|
dbTransfer2 := &db.Transfer{Value: 125000, Timestamp: transferTime2}
|
|
transfer2, err := newTransferFromDbTransfer(dbTransfer2)
|
|
require.NoError(t, err)
|
|
transfers = append(transfers, transfer2)
|
|
|
|
assert.Equal(t, 2, len(transfers))
|
|
|
|
sum, updatedTransfers, err := gov.TrimAndSumValue(transfers, now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(updatedTransfers))
|
|
assert.Equal(t, int64(0), sum)
|
|
}
|
|
|
|
func newChainGovernorForTest(ctx context.Context) (*ChainGovernor, error) {
|
|
return newChainGovernorForTestWithLogger(ctx, zap.NewNop())
|
|
}
|
|
|
|
func newChainGovernorForTestWithLogger(ctx context.Context, logger *zap.Logger) (*ChainGovernor, error) {
|
|
if ctx == nil {
|
|
return nil, fmt.Errorf("ctx is nil")
|
|
}
|
|
|
|
var db db.MockGovernorDB
|
|
gov := NewChainGovernor(logger, &db, common.GoTest, true)
|
|
|
|
err := gov.Run(ctx)
|
|
if err != nil {
|
|
return gov, err
|
|
}
|
|
|
|
emitterAddr, err := vaa.StringToAddress("0x0290fb167208af455bb137780163b7b7a9a10c16")
|
|
if err != nil {
|
|
return gov, err
|
|
}
|
|
|
|
tokenAddr, err := vaa.StringToAddress("0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E")
|
|
if err != nil {
|
|
return gov, err
|
|
}
|
|
|
|
gov.initConfigForTest(
|
|
vaa.ChainIDEthereum,
|
|
emitterAddr,
|
|
1000000,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddr,
|
|
"WETH",
|
|
1774.62,
|
|
8,
|
|
)
|
|
|
|
return gov, nil
|
|
}
|
|
|
|
// Converts a string into a go-ethereum Hash object used as test input.
|
|
func hashFromString(str string) eth_common.Hash {
|
|
if (len(str) > 2) && (str[0] == '0') && (str[1] == 'x') {
|
|
str = str[2:]
|
|
}
|
|
|
|
return eth_common.HexToHash(str)
|
|
}
|
|
|
|
func TestVaaForUninterestingEmitterChain(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
emitterAddr, _ := vaa.StringToAddress("0x00")
|
|
payload := []byte{1, 97, 97, 97, 97, 97}
|
|
|
|
msg := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDSolana,
|
|
EmitterAddress: emitterAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payload,
|
|
}
|
|
|
|
canPost, err := gov.ProcessMsgForTime(&msg, time.Now())
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
}
|
|
|
|
func TestVaaForUninterestingEmitterAddress(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
emitterAddr, _ := vaa.StringToAddress("0x00")
|
|
payload := []byte{1, 97, 97, 97, 97, 97}
|
|
|
|
msg := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: emitterAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payload,
|
|
}
|
|
|
|
canPost, err := gov.ProcessMsgForTime(&msg, time.Now())
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 0, len(gov.msgsSeen))
|
|
}
|
|
|
|
func TestVaaForUninterestingPayloadType(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
emitterAddr, _ := vaa.StringToAddress("0x0290fb167208af455bb137780163b7b7a9a10c16")
|
|
payload := []byte{2, 97, 97, 97, 97, 97}
|
|
|
|
msg := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: emitterAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payload,
|
|
}
|
|
|
|
canPost, err := gov.ProcessMsgForTime(&msg, time.Now())
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 0, len(gov.msgsSeen))
|
|
}
|
|
|
|
// Note this method assumes 18 decimals for the amount.
|
|
func buildMockTransferPayloadBytes(
|
|
t uint8,
|
|
tokenChainID vaa.ChainID,
|
|
tokenAddrStr string,
|
|
toChainID vaa.ChainID,
|
|
toAddrStr string,
|
|
amtFloat float64,
|
|
) []byte {
|
|
bytes := make([]byte, 101)
|
|
bytes[0] = t
|
|
|
|
amtBigFloat := big.NewFloat(amtFloat)
|
|
amtBigFloat = amtBigFloat.Mul(amtBigFloat, big.NewFloat(100000000))
|
|
amount, _ := amtBigFloat.Int(nil)
|
|
amtBytes := amount.Bytes()
|
|
if len(amtBytes) > 32 {
|
|
panic("amount will not fit in 32 bytes!")
|
|
}
|
|
copy(bytes[33-len(amtBytes):33], amtBytes)
|
|
|
|
tokenAddr, _ := vaa.StringToAddress(tokenAddrStr)
|
|
copy(bytes[33:65], tokenAddr.Bytes())
|
|
binary.BigEndian.PutUint16(bytes[65:67], uint16(tokenChainID))
|
|
toAddr, _ := vaa.StringToAddress(toAddrStr)
|
|
copy(bytes[67:99], toAddr.Bytes())
|
|
binary.BigEndian.PutUint16(bytes[99:101], uint16(toChainID))
|
|
return bytes
|
|
}
|
|
|
|
func TestBuidMockTransferPayload(t *testing.T) {
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
payloadBytes := buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
1.25,
|
|
)
|
|
|
|
payload, err := vaa.DecodeTransferPayloadHdr(payloadBytes)
|
|
require.NoError(t, err)
|
|
|
|
expectedTokenAddr, err := vaa.StringToAddress(tokenAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
expectedToAddr, err := vaa.StringToAddress(toAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
expected := &vaa.TransferPayloadHdr{
|
|
Type: 1,
|
|
Amount: big.NewInt(125000000),
|
|
OriginAddress: expectedTokenAddr,
|
|
OriginChain: vaa.ChainIDEthereum,
|
|
TargetAddress: expectedToAddr,
|
|
TargetChain: vaa.ChainIDPolygon,
|
|
}
|
|
|
|
assert.Equal(t, expected, payload)
|
|
}
|
|
|
|
func TestVaaForUninterestingToken(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
uninterestingTokenAddrStr := "0x42"
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
payloadBytes := buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
uninterestingTokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
1.25,
|
|
)
|
|
|
|
tokenBridgeAddr, _ := vaa.StringToAddress("0x0290fb167208af455bb137780163b7b7a9a10c16")
|
|
|
|
msg := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payloadBytes,
|
|
}
|
|
|
|
canPost, err := gov.ProcessMsgForTime(&msg, time.Now())
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 0, len(gov.msgsSeen))
|
|
}
|
|
|
|
// Test the flow cancel mechanism at the resolution of the ProcessMsgForTime (VAA parsing)
|
|
// This test simulates a transaction of a flow-cancelling asset from one chain to another and back.
|
|
// After this operation, we verify that the net flow across these chains is zero but that the
|
|
// transfers have indeed been processed.
|
|
// Finally a regular (non flow-cancelling) transfer is added just to ensure we aren't testing some empty/nil/0 case.
|
|
// The flow cancelling asset has an origin chain that is different from the emitter chain to demonstrate
|
|
// that these values don't have to match.
|
|
func TestFlowCancelProcessMsgForTimeFullCancel(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
// Set-up time
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
transferTime := time.Unix(int64(1654543099), 0)
|
|
|
|
// Solana USDC used as the flow cancelling asset. This ensures that the flow cancel mechanism works
|
|
// when the Origin chain of the asset does not match the emitter chain
|
|
// NOTE: Replace this Chain:Address pair if the Flow Cancel Token List is modified
|
|
var flowCancelTokenOriginAddress vaa.Address
|
|
flowCancelTokenOriginAddress, err = vaa.StringToAddress("c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61")
|
|
require.NoError(t, err)
|
|
|
|
var notFlowCancelTokenOriginAddress vaa.Address
|
|
notFlowCancelTokenOriginAddress, err = vaa.StringToAddress("77777af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f7777")
|
|
require.NoError(t, err)
|
|
|
|
// Data for Ethereum
|
|
tokenBridgeAddrStrEthereum := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddrEthereum, err := vaa.StringToAddress(tokenBridgeAddrStrEthereum)
|
|
require.NoError(t, err)
|
|
recipientEthereum := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8" //nolint:gosec
|
|
|
|
// Data for Sui
|
|
tokenBridgeAddrStrSui := "0xc57508ee0d4595e5a8728974a4a93a787d38f339757230d441e895422c07aba9" //nolint:gosec
|
|
tokenBridgeAddrSui, err := vaa.StringToAddress(tokenBridgeAddrStrSui)
|
|
require.NoError(t, err)
|
|
recipientSui := "0x84a5f374d29fc77e370014dce4fd6a55b58ad608de8074b0be5571701724da31"
|
|
|
|
// Data for Solana. Only used to represent the flow cancel asset.
|
|
// "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb"
|
|
tokenBridgeAddrStrSolana := "0x0e0a589e6488147a94dcfa592b90fdd41152bb2ca77bf6016758a6f4df9d21b4" //nolint:gosec
|
|
|
|
// Add chain entries to `gov`
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStrEthereum, 10000, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setChainForTesting(vaa.ChainIDSui, tokenBridgeAddrStrSui, 10000, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setChainForTesting(vaa.ChainIDSolana, tokenBridgeAddrStrSolana, 10000, 0)
|
|
require.NoError(t, err)
|
|
|
|
// Add flow cancel asset and non-flow cancelable asset to the token entry for `gov`
|
|
err = gov.setTokenForTesting(vaa.ChainIDSolana, flowCancelTokenOriginAddress.String(), "USDC", 1.0, true)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov.tokens[tokenKey{chain: vaa.ChainIDSolana, addr: flowCancelTokenOriginAddress}])
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, notFlowCancelTokenOriginAddress.String(), "NOTCANCELABLE", 1.0, false)
|
|
require.NoError(t, err)
|
|
|
|
// Transfer from Ethereum to Sui via the token bridge
|
|
msg1 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: transferTime,
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddrEthereum,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDSolana, // The origin asset for the token being transferred
|
|
flowCancelTokenOriginAddress.String(),
|
|
vaa.ChainIDSui, // destination chain of the transfer
|
|
recipientSui,
|
|
5000,
|
|
),
|
|
}
|
|
|
|
// Transfer from Sui to Ethereum via the token bridge
|
|
msg2 := common.MessagePublication{
|
|
TxHash: hashFromString("0xabc123f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4064"),
|
|
Timestamp: transferTime,
|
|
Nonce: uint32(2),
|
|
Sequence: uint64(2),
|
|
EmitterChain: vaa.ChainIDSui,
|
|
EmitterAddress: tokenBridgeAddrSui,
|
|
ConsistencyLevel: uint8(0), // Sui has a consistency level of 0 (instant)
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDSolana, // Asset is owned by Solana chain. That's all we care about here.
|
|
flowCancelTokenOriginAddress.String(),
|
|
vaa.ChainIDEthereum, // destination chain
|
|
recipientEthereum,
|
|
1000,
|
|
),
|
|
}
|
|
|
|
// msg and asset that are NOT flow cancelable
|
|
msg3 := common.MessagePublication{
|
|
TxHash: hashFromString("0x888888f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a8888"),
|
|
Timestamp: time.Unix(int64(transferTime.Unix()+1), 0),
|
|
Nonce: uint32(3),
|
|
Sequence: uint64(3),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddrEthereum,
|
|
ConsistencyLevel: uint8(0), // Sui has a consistency level of 0 (instant)
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum, // Asset is owned by Ethereum chain. That's all we care about here.
|
|
notFlowCancelTokenOriginAddress.String(),
|
|
vaa.ChainIDSui,
|
|
recipientSui,
|
|
1500,
|
|
),
|
|
}
|
|
|
|
// Stage 0: No transfers sent
|
|
chainEntryEthereum, exists := gov.chains[vaa.ChainIDEthereum]
|
|
assert.True(t, exists)
|
|
assert.NotNil(t, chainEntryEthereum)
|
|
chainEntrySui, exists := gov.chains[vaa.ChainIDSui]
|
|
assert.True(t, exists)
|
|
assert.NotNil(t, chainEntrySui)
|
|
sumEth, ethTransfers, err := gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, len(ethTransfers))
|
|
assert.Zero(t, sumEth)
|
|
require.NoError(t, err)
|
|
sumSui, suiTransfers, err := gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(1654543099), 0))
|
|
assert.Zero(t, len(suiTransfers))
|
|
assert.Zero(t, sumSui)
|
|
require.NoError(t, err)
|
|
|
|
// Perform a FIRST transfer (Ethereum --> Sui)
|
|
result, err := gov.ProcessMsgForTime(&msg1, time.Now())
|
|
assert.True(t, result)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 2, numTrans) // One for the positive and one for the negative
|
|
assert.Equal(t, int64(0), valueTrans) // Zero! Cancel flow token!
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// Check the state of the governor
|
|
chainEntryEthereum = gov.chains[vaa.ChainIDEthereum]
|
|
chainEntrySui = gov.chains[vaa.ChainIDSui]
|
|
assert.Equal(t, int(1), len(chainEntryEthereum.transfers))
|
|
assert.Equal(t, int(1), len(chainEntrySui.transfers))
|
|
sumEth, ethTransfers, err = gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(5000), sumEth) // Outbound on Ethereum
|
|
assert.Equal(t, int(1), len(ethTransfers))
|
|
require.NoError(t, err)
|
|
|
|
// Outbound check:
|
|
// - ensure that the sum of the transfers is equal to the value of the inverse transfer
|
|
// - ensure the actual governor usage is Zero (any negative value is converted to zero by TrimAndSumValueForChain)
|
|
sumSui, suiTransfers, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, 1, len(suiTransfers)) // A single NEGATIVE transfer
|
|
assert.Equal(t, int64(-5000), sumSui) // Ensure the inverse (negative) transfer is in the Sui chain Entry
|
|
require.NoError(t, err)
|
|
suiGovernorUsage, err := gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, suiGovernorUsage) // Actual governor usage must not be negative.
|
|
require.NoError(t, err)
|
|
|
|
// Perform a SECOND transfer (Sui --> Ethereum)
|
|
result, err = gov.ProcessMsgForTime(&msg2, time.Now())
|
|
assert.True(t, result)
|
|
require.NoError(t, err)
|
|
|
|
// Stage 2: Transfer sent from Sui to Ethereum.
|
|
// This transfer should result in some flow cancelling on Ethereum so we assert that its sum has decreased
|
|
// compared to the previous step.
|
|
// Check the governor stats both with respect to flow cancelling and to the actual value that has moved.
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 2, len(gov.msgsSeen)) // Two messages observed
|
|
assert.Equal(t, 4, numTrans) // Two messages, but four transfers because inverses are added.
|
|
assert.Equal(t, int64(0), valueTrans) // The two transfers and their inverses cancel each other out.
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
// Verify the stats that are non flow-cancelling.
|
|
// In practice this is the sum of the absolute value of all the transfers.
|
|
// 5000 * 2 + 1000 * 2 = 12000
|
|
_, absValueTrans, _, _ := gov.getStatsForAllChains()
|
|
assert.Equal(t, uint64(12000), absValueTrans)
|
|
|
|
// Check the state of the governor.
|
|
chainEntryEthereum = gov.chains[vaa.ChainIDEthereum]
|
|
chainEntrySui = gov.chains[vaa.ChainIDSui]
|
|
assert.Equal(t, int(2), len(chainEntryEthereum.transfers)) // One for inbound refund and another for outbound
|
|
assert.Equal(t, int(2), len(chainEntrySui.transfers)) // One for inbound refund and another for outbound
|
|
sumEth, ethTransfers, err = gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(4000), sumEth) // Out was 5000 then the cancellation makes this 4000.
|
|
assert.Equal(t, int(2), len(ethTransfers)) // Two transfers: outbound 5000 and inverse -1000 transfer
|
|
require.NoError(t, err)
|
|
sumSui, suiTransfers, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int(2), len(suiTransfers))
|
|
assert.Equal(t, int64(-4000), sumSui) // -5000 from Ethereum inverse added to 1000 from sending to Ethereum
|
|
require.NoError(t, err)
|
|
suiGovernorUsage, err = gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, suiGovernorUsage) // Actual governor usage must not be negative.
|
|
require.NoError(t, err)
|
|
|
|
// Message for a non-flow cancellable token (Ethereum --> Sui)
|
|
result, err = gov.ProcessMsgForTime(&msg3, time.Now())
|
|
assert.True(t, result)
|
|
require.NoError(t, err)
|
|
|
|
// Stage 3: Asset withoout flow cancelling has also been sent
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 3, len(gov.msgsSeen))
|
|
assert.Equal(t, 5, numTrans) // Only a single new transfer for the positive change
|
|
assert.Equal(t, int64(1500), valueTrans) // Consume 1500 capacity on Ethereum
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
// Verify the stats that are non flow-cancelling.
|
|
// In practice this is the sum of the absolute value of all the transfers.
|
|
// 5000 * 2 + 1000 * 2 + 1500 = 13500
|
|
_, absValueTrans, _, _ = gov.getStatsForAllChains()
|
|
assert.Equal(t, uint64(13500), absValueTrans) // The net actual flow of assets is 4000 (after cancelling) plus 1500
|
|
|
|
// Check the state of the governor
|
|
chainEntryEthereum = gov.chains[vaa.ChainIDEthereum]
|
|
chainEntrySui = gov.chains[vaa.ChainIDSui]
|
|
assert.Equal(t, int(3), len(chainEntryEthereum.transfers)) // One for inbound refund and another for outbound
|
|
assert.Equal(t, int(2), len(chainEntrySui.transfers)) // One for inbound refund and another for outbound
|
|
sumEth, ethTransfers, err = gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(5500), sumEth) // The value of the non-cancelled transfer
|
|
assert.Equal(t, int(3), len(ethTransfers)) // Two transfers cancel each other out
|
|
require.NoError(t, err)
|
|
sumSui, suiTransfers, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int(2), len(suiTransfers))
|
|
assert.Equal(t, int64(-4000), sumSui) // Sui's limit should not change
|
|
require.NoError(t, err)
|
|
suiGovernorUsage, err = gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, suiGovernorUsage) // Actual governor usage must not be negative.
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Test the flow cancel mechanism at the resolution of the ProcessMsgForTime (VAA parsing)
|
|
// This test checks a flow cancel scenario where the amounts don't completely cancel each other
|
|
// out.
|
|
// It also highlights the differences between the following values:
|
|
// - Governor stats for chains: the sum of the absolute values of all transfers
|
|
// - Governor stats for chains, flow cancelling: the sum of transfer values, including 'inverse' transfers
|
|
// - The sum of transfers in a chain entry: The sum of outbound transfers and inbound flow cancelling transfers for a chain
|
|
// - The Governor usage for a chain: Same as above but saturates to 0 as a lower bound
|
|
func TestFlowCancelProcessMsgForTimePartialCancel(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
// Set-up time
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
transferTime := time.Unix(int64(1654543099), 0)
|
|
|
|
// Solana USDC used as the flow cancelling asset. This ensures that the flow cancel mechanism works
|
|
// when the Origin chain of the asset does not match the emitter chain
|
|
// NOTE: Replace this Chain:Address pair if the Flow Cancel Token List is modified
|
|
var flowCancelTokenOriginAddress vaa.Address
|
|
flowCancelTokenOriginAddress, err = vaa.StringToAddress("c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61")
|
|
require.NoError(t, err)
|
|
|
|
var notFlowCancelTokenOriginAddress vaa.Address
|
|
notFlowCancelTokenOriginAddress, err = vaa.StringToAddress("77777af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f7777")
|
|
require.NoError(t, err)
|
|
|
|
// Data for Ethereum
|
|
tokenBridgeAddrStrEthereum := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddrEthereum, err := vaa.StringToAddress(tokenBridgeAddrStrEthereum)
|
|
require.NoError(t, err)
|
|
recipientEthereum := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8" //nolint:gosec
|
|
|
|
// Data for Sui
|
|
tokenBridgeAddrStrSui := "0xc57508ee0d4595e5a8728974a4a93a787d38f339757230d441e895422c07aba9" //nolint:gosec
|
|
tokenBridgeAddrSui, err := vaa.StringToAddress(tokenBridgeAddrStrSui)
|
|
require.NoError(t, err)
|
|
recipientSui := "0x84a5f374d29fc77e370014dce4fd6a55b58ad608de8074b0be5571701724da31" //nolint:gosec
|
|
|
|
// Add chain entries to `gov`
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStrEthereum, 10000, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setChainForTesting(vaa.ChainIDSui, tokenBridgeAddrStrSui, 10000, 0)
|
|
require.NoError(t, err)
|
|
|
|
// Add flow cancel asset and non-flow cancelable asset to the token entry for `gov`
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, flowCancelTokenOriginAddress.String(), "USDC", 1.0, true)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov.tokens[tokenKey{chain: vaa.ChainIDEthereum, addr: flowCancelTokenOriginAddress}])
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, notFlowCancelTokenOriginAddress.String(), "NOTCANCELABLE", 2.5, false)
|
|
require.NoError(t, err)
|
|
|
|
// Transfer from Ethereum to Sui via the token bridge
|
|
msg1 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: transferTime,
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddrEthereum,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum, // The origin asset for the token being transferred
|
|
flowCancelTokenOriginAddress.String(),
|
|
vaa.ChainIDSui, // destination chain of the transfer
|
|
recipientSui,
|
|
5000,
|
|
),
|
|
}
|
|
|
|
// Transfer from Sui to Ethereum via the token bridge
|
|
msg2 := common.MessagePublication{
|
|
TxHash: hashFromString("0xabc123f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4064"),
|
|
Timestamp: transferTime,
|
|
Nonce: uint32(2),
|
|
Sequence: uint64(2),
|
|
EmitterChain: vaa.ChainIDSui,
|
|
EmitterAddress: tokenBridgeAddrSui,
|
|
ConsistencyLevel: uint8(0), // Sui has a consistency level of 0 (instant)
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum, // Asset is owned by Ethereum chain. That's all we care about here.
|
|
flowCancelTokenOriginAddress.String(),
|
|
vaa.ChainIDEthereum, // destination chain
|
|
recipientEthereum,
|
|
5000,
|
|
),
|
|
}
|
|
|
|
// msg and asset that are NOT flow cancelable
|
|
msg3 := common.MessagePublication{
|
|
TxHash: hashFromString("0x888888f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a8888"),
|
|
Timestamp: time.Unix(int64(transferTime.Unix()+1), 0),
|
|
Nonce: uint32(3),
|
|
Sequence: uint64(3),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddrEthereum,
|
|
ConsistencyLevel: uint8(0), // Sui has a consistency level of 0 (instant)
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum, // Asset is owned by Ethereum chain. That's all we care about here.
|
|
notFlowCancelTokenOriginAddress.String(),
|
|
vaa.ChainIDSui,
|
|
recipientSui,
|
|
1000, // Note that this asset is worth 2.5 USD, so the notional value is 2500
|
|
),
|
|
}
|
|
|
|
// Stage 0: No transfers sent
|
|
chainEntryEthereum, exists := gov.chains[vaa.ChainIDEthereum]
|
|
assert.True(t, exists)
|
|
assert.NotNil(t, chainEntryEthereum)
|
|
chainEntrySui, exists := gov.chains[vaa.ChainIDSui]
|
|
assert.True(t, exists)
|
|
assert.NotNil(t, chainEntrySui)
|
|
sumEth, ethTransfers, err := gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, len(ethTransfers))
|
|
assert.Zero(t, sumEth)
|
|
require.NoError(t, err)
|
|
sumSui, suiTransfers, err := gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(1654543099), 0))
|
|
assert.Zero(t, len(suiTransfers))
|
|
assert.Zero(t, sumSui)
|
|
require.NoError(t, err)
|
|
|
|
result, err := gov.ProcessMsgForTime(&msg1, time.Now())
|
|
assert.True(t, result)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 2, numTrans) // One for the positive and one for the negative
|
|
assert.Equal(t, int64(0), valueTrans) // Zero! Cancel flow token!
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// Check the state of the governor
|
|
chainEntryEthereum = gov.chains[vaa.ChainIDEthereum]
|
|
chainEntrySui = gov.chains[vaa.ChainIDSui]
|
|
assert.Equal(t, int(1), len(chainEntryEthereum.transfers))
|
|
assert.Equal(t, int(1), len(chainEntrySui.transfers))
|
|
sumEth, ethTransfers, err = gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(5000), sumEth) // Outbound on Ethereum
|
|
assert.Equal(t, int(1), len(ethTransfers))
|
|
require.NoError(t, err)
|
|
|
|
// Outbound check:
|
|
// - ensure that the sum of the transfers is equal to the value of the inverse transfer
|
|
// - ensure the actual governor usage is Zero (any negative value is converted to zero by TrimAndSumValueForChain)
|
|
sumSui, suiTransfers, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, 1, len(suiTransfers)) // A single NEGATIVE transfer
|
|
assert.Equal(t, int64(-5000), sumSui) // Ensure the inverse (negative) transfer is in the Sui chain Entry
|
|
require.NoError(t, err)
|
|
suiGovernorUsage, err := gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, suiGovernorUsage) // Actual governor usage must not be negative.
|
|
require.NoError(t, err)
|
|
|
|
// Perform a SECOND transfer (Sui --> Ethereum)
|
|
result, err = gov.ProcessMsgForTime(&msg2, time.Now())
|
|
assert.True(t, result)
|
|
require.NoError(t, err)
|
|
|
|
// Stage 2: Transfer sent from Sui to Ethereum.
|
|
// This transfer should result in flow cancelling on Ethereum so we assert that its sum has decreased
|
|
// compared to the previous step.
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 2, len(gov.msgsSeen)) // Two messages observed
|
|
assert.Equal(t, 4, numTrans) // Two messages, but four transfers because inverses are added.
|
|
assert.Equal(t, int64(0), valueTrans) // New flow is zero! Cancel flow token!
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
|
|
// Check the state of the governor. Confirm that both chains have two transfers but have cancelled
|
|
// each other out in terms of the summed values.
|
|
chainEntryEthereum = gov.chains[vaa.ChainIDEthereum]
|
|
chainEntrySui = gov.chains[vaa.ChainIDSui]
|
|
assert.Equal(t, int(2), len(chainEntryEthereum.transfers)) // One for inbound refund and another for outbound
|
|
assert.Equal(t, int(2), len(chainEntrySui.transfers)) // One for inbound refund and another for outbound
|
|
sumEth, ethTransfers, err = gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(0), sumEth) // Out was 4000 then the cancellation makes this zero.
|
|
assert.Equal(t, int(2), len(ethTransfers)) // Two transfers cancel each other out
|
|
require.NoError(t, err)
|
|
sumSui, suiTransfers, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int(2), len(suiTransfers))
|
|
assert.Equal(t, int64(0), sumSui)
|
|
require.NoError(t, err)
|
|
|
|
// Message for a non-flow cancellable token (Ethereum --> Sui)
|
|
result, err = gov.ProcessMsgForTime(&msg3, time.Now())
|
|
assert.True(t, result)
|
|
require.NoError(t, err)
|
|
|
|
// Stage 3: Asset withoout flow cancelling has also been sent
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 3, len(gov.msgsSeen))
|
|
assert.Equal(t, 5, numTrans) // Only a single new transfer for the positive change
|
|
assert.Equal(t, int64(2500), valueTrans) // Change in value from the transfer: 1000 tokens worth $2.5 USD
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
|
|
// Check the state of the governor
|
|
chainEntryEthereum = gov.chains[vaa.ChainIDEthereum]
|
|
chainEntrySui = gov.chains[vaa.ChainIDSui]
|
|
assert.Equal(t, int(3), len(chainEntryEthereum.transfers)) // One for inbound refund and another for outbound
|
|
assert.Equal(t, int(2), len(chainEntrySui.transfers)) // One for inbound refund and another for outbound
|
|
sumEth, ethTransfers, err = gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(2500), sumEth) // The value of the non-cancelled transfer
|
|
assert.Equal(t, int(3), len(ethTransfers)) // Two transfers cancel each other out
|
|
require.NoError(t, err)
|
|
sumSui, suiTransfers, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int(2), len(suiTransfers))
|
|
assert.Equal(t, int64(0), sumSui) // Sui's limit is still zero
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestTransfersUpToAndOverTheLimit(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 1000000, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
payloadBytes1 := buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
1.25,
|
|
)
|
|
|
|
// The first two transfers should be accepted.
|
|
msg1 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payloadBytes1,
|
|
}
|
|
|
|
msg2 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(2),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payloadBytes1,
|
|
}
|
|
|
|
canPost, err := gov.ProcessMsgForTime(&msg1, time.Now())
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 1, numTrans)
|
|
assert.Equal(t, uint64(2218), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
canPost, err = gov.ProcessMsgForTime(&msg2, time.Now())
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(4436), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 2, len(gov.msgsSeen))
|
|
|
|
// But the third one should be queued up.
|
|
payloadBytes2 := buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
1250,
|
|
)
|
|
|
|
msg3 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(3),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payloadBytes2,
|
|
}
|
|
|
|
canPost, err = gov.ProcessMsgForTime(&msg3, time.Now())
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(4436), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(2218274), valuePending)
|
|
assert.Equal(t, 3, len(gov.msgsSeen))
|
|
|
|
// But a small one should still go through.
|
|
msg4 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(4),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payloadBytes1,
|
|
}
|
|
|
|
canPost, err = gov.ProcessMsgForTime(&msg4, time.Now())
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 3, numTrans)
|
|
assert.Equal(t, uint64(4436+2218), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(2218274), valuePending)
|
|
assert.Equal(t, 4, len(gov.msgsSeen))
|
|
}
|
|
|
|
func TestPendingTransferBeingReleased(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 1000000, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
// The first VAA should be accepted.
|
|
payloadBytes1 := buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
270,
|
|
)
|
|
|
|
msg1 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payloadBytes1,
|
|
}
|
|
|
|
now, _ := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
canPost, err := gov.ProcessMsgForTime(&msg1, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 1, numTrans)
|
|
assert.Equal(t, uint64(479147), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// And so should the second.
|
|
payloadBytes2 := buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
275,
|
|
)
|
|
|
|
msg2 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payloadBytes2,
|
|
}
|
|
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 6:00pm (CST)")
|
|
canPost, err = gov.ProcessMsgForTime(&msg2, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(479147+488020), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 2, len(gov.msgsSeen))
|
|
|
|
// But the third one should be queued up.
|
|
payloadBytes3 := buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
280,
|
|
)
|
|
|
|
msg3 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payloadBytes3,
|
|
}
|
|
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 2:00am (CST)")
|
|
canPost, err = gov.ProcessMsgForTime(&msg3, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(479147+488020), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(496893), valuePending)
|
|
assert.Equal(t, 3, len(gov.msgsSeen))
|
|
|
|
// And so should the fourth one.
|
|
payloadBytes4 := buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
300,
|
|
)
|
|
|
|
msg4 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payloadBytes4,
|
|
}
|
|
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 8:00am (CST)")
|
|
canPost, err = gov.ProcessMsgForTime(&msg4, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(479147+488020), valueTrans)
|
|
assert.Equal(t, 2, numPending)
|
|
assert.Equal(t, uint64(496893+532385), valuePending)
|
|
assert.Equal(t, 4, len(gov.msgsSeen))
|
|
|
|
// If we check pending before noon, nothing should happen.
|
|
now, err = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 9:00am (CST)")
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, now)
|
|
toBePublished, err := gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(toBePublished))
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(479147+488020), valueTrans)
|
|
assert.Equal(t, 2, numPending)
|
|
assert.Equal(t, uint64(496893+532385), valuePending)
|
|
assert.Equal(t, 4, len(gov.msgsSeen))
|
|
|
|
// But at 3pm, the first one should drop off and the first queued one should get posted.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 3:00pm (CST)")
|
|
toBePublished, err = gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(toBePublished))
|
|
checkTargetOnReleasedIsSet(t, toBePublished, vaa.ChainIDPolygon, toAddrStr)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(488020+496893), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(532385), valuePending)
|
|
assert.Equal(t, 3, len(gov.msgsSeen))
|
|
}
|
|
|
|
func TestPopulateChainIds(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
// Sanity check
|
|
assert.NotZero(t, len(gov.chainIds))
|
|
|
|
// Ensure that the chainIds slice match the entries in the chains map
|
|
assert.Equal(t, len(gov.chains), len(gov.chainIds))
|
|
lowest := 0
|
|
for _, chainId := range gov.chainIds {
|
|
chainEntry, ok := gov.chains[chainId]
|
|
assert.NotNil(t, chainEntry)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, chainEntry.emitterChainId, chainId)
|
|
// Check that the chainIds are in ascending order. The point of this slice is that it provides
|
|
// deterministic ordering over chainIds.
|
|
assert.Greater(t, int(chainId), lowest)
|
|
lowest = int(chainId)
|
|
}
|
|
}
|
|
|
|
// Test that, when a small transfer (under the 'big tx limit') of a flow-cancelling asset is queued and
|
|
// later released, it causes a reduction in the Governor usage for the destination chain.
|
|
func TestPendingTransferFlowCancelsWhenReleased(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
// Set-up time
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
transferTime := time.Unix(int64(1654543099), 0)
|
|
|
|
// Solana USDC used as the flow cancelling asset. This ensures that the flow cancel mechanism works
|
|
// when the Origin chain of the asset does not match the emitter chain
|
|
// NOTE: Replace this Chain:Address pair if the Flow Cancel Token List is modified
|
|
var flowCancelTokenOriginAddress vaa.Address
|
|
flowCancelTokenOriginAddress, err = vaa.StringToAddress("c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61")
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, err)
|
|
|
|
// Data for Ethereum
|
|
tokenBridgeAddrStrEthereum := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddrEthereum, err := vaa.StringToAddress(tokenBridgeAddrStrEthereum)
|
|
require.NoError(t, err)
|
|
recipientEthereum := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8" //nolint:gosec
|
|
|
|
// Data for Sui
|
|
tokenBridgeAddrStrSui := "0xc57508ee0d4595e5a8728974a4a93a787d38f339757230d441e895422c07aba9" //nolint:gosec
|
|
tokenBridgeAddrSui, err := vaa.StringToAddress(tokenBridgeAddrStrSui)
|
|
require.NoError(t, err)
|
|
recipientSui := "0x84a5f374d29fc77e370014dce4fd6a55b58ad608de8074b0be5571701724da31"
|
|
|
|
// Data for Solana. Only used to represent the flow cancel asset.
|
|
// "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb"
|
|
tokenBridgeAddrStrSolana := "0x0e0a589e6488147a94dcfa592b90fdd41152bb2ca77bf6016758a6f4df9d21b4" //nolint:gosec
|
|
|
|
// Add chain entries to `gov`
|
|
dailyLimit := uint64(10000)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStrEthereum, dailyLimit, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setChainForTesting(vaa.ChainIDSui, tokenBridgeAddrStrSui, dailyLimit, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setChainForTesting(vaa.ChainIDSolana, tokenBridgeAddrStrSolana, dailyLimit, 0)
|
|
require.NoError(t, err)
|
|
|
|
// Add flow cancel asset and non-flow cancelable asset to the token entry for `gov`
|
|
err = gov.setTokenForTesting(vaa.ChainIDSolana, flowCancelTokenOriginAddress.String(), "USDC", 1.0, true)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov.tokens[tokenKey{chain: vaa.ChainIDSolana, addr: flowCancelTokenOriginAddress}])
|
|
|
|
// First message: consume most of the dailyLimit for the emitter chain
|
|
msg1 := common.MessagePublication{
|
|
TxHash: hashFromString("0x888888f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a8888"),
|
|
Timestamp: time.Unix(int64(transferTime.Unix()+1), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddrEthereum,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDSolana, // The origin asset for the token being transferred
|
|
flowCancelTokenOriginAddress.String(),
|
|
vaa.ChainIDSui,
|
|
recipientSui,
|
|
10000,
|
|
),
|
|
}
|
|
|
|
// Second message: This transfer gets queued because the limit is exhausted
|
|
msg2 := common.MessagePublication{
|
|
TxHash: hashFromString("0x888888f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a8888"),
|
|
Timestamp: time.Unix(int64(transferTime.Unix()+2), 0),
|
|
Nonce: uint32(2),
|
|
Sequence: uint64(2),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddrEthereum,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDSolana,
|
|
flowCancelTokenOriginAddress.String(),
|
|
vaa.ChainIDSui,
|
|
recipientSui,
|
|
500,
|
|
),
|
|
}
|
|
|
|
// Third message: Incoming flow cancelling transfer to the emitter chain for the previous messages. This
|
|
// reduces the Governor usage for that chain.
|
|
msg3 := common.MessagePublication{
|
|
TxHash: hashFromString("0x888888f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a8888"),
|
|
Timestamp: time.Unix(int64(transferTime.Unix()+3), 0),
|
|
Nonce: uint32(3),
|
|
Sequence: uint64(3),
|
|
EmitterChain: vaa.ChainIDSui,
|
|
EmitterAddress: tokenBridgeAddrSui,
|
|
ConsistencyLevel: uint8(0), // Sui has a consistency level of 0 (instant)
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDSolana,
|
|
flowCancelTokenOriginAddress.String(),
|
|
vaa.ChainIDEthereum,
|
|
recipientEthereum,
|
|
1000,
|
|
),
|
|
}
|
|
|
|
// Stage 0: No transfers sent
|
|
chainEntryEthereum, exists := gov.chains[vaa.ChainIDEthereum]
|
|
assert.True(t, exists)
|
|
assert.NotNil(t, chainEntryEthereum)
|
|
chainEntrySui, exists := gov.chains[vaa.ChainIDSui]
|
|
assert.True(t, exists)
|
|
assert.NotNil(t, chainEntrySui)
|
|
sumEth, ethTransfers, err := gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, len(ethTransfers))
|
|
assert.Zero(t, len(chainEntryEthereum.pending))
|
|
assert.Zero(t, sumEth)
|
|
require.NoError(t, err)
|
|
sumSui, suiTransfers, err := gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(1654543099), 0))
|
|
assert.Zero(t, len(suiTransfers))
|
|
assert.Zero(t, sumSui)
|
|
require.NoError(t, err)
|
|
|
|
// Perform a FIRST transfer (Ethereum --> Sui)
|
|
result, err := gov.ProcessMsgForTime(&msg1, time.Now())
|
|
assert.True(t, result)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, netValueTrans, numPending, valuePending := gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 2, numTrans) // One for the positive and one for the negative
|
|
assert.Equal(t, int64(0), netValueTrans) // Zero, because the asset flow cancels
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// Check the state of the governor
|
|
chainEntryEthereum = gov.chains[vaa.ChainIDEthereum]
|
|
chainEntrySui = gov.chains[vaa.ChainIDSui]
|
|
assert.Equal(t, int(1), len(chainEntryEthereum.transfers))
|
|
assert.Equal(t, int(0), len(chainEntryEthereum.pending)) // One for inbound refund and another for outbound
|
|
assert.Equal(t, int(1), len(chainEntrySui.transfers))
|
|
sumEth, ethTransfers, err = gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(10000), sumEth) // Equal to total dailyLimit
|
|
assert.Equal(t, int(1), len(ethTransfers))
|
|
require.NoError(t, err)
|
|
|
|
// Outbound check:
|
|
// - ensure that the sum of the transfers is equal to the value of the inverse transfer
|
|
// - ensure the actual governor usage is Zero (any negative value is converted to zero by TrimAndSumValueForChain)
|
|
sumSui, suiTransfers, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, 1, len(suiTransfers)) // A single NEGATIVE transfer
|
|
assert.Equal(t, int64(-10000), sumSui) // Ensure the inverse (negative) transfer is in the Sui chain Entry
|
|
require.NoError(t, err)
|
|
suiGovernorUsage, err := gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, suiGovernorUsage) // Actual governor usage must not be negative.
|
|
require.NoError(t, err)
|
|
|
|
// Perform a SECOND transfer (Ethereum --> Sui again)
|
|
// When a transfer is queued, ProcessMsgForTime should return false.
|
|
result, err = gov.ProcessMsgForTime(&msg2, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.False(t, result)
|
|
require.NoError(t, err)
|
|
|
|
// Stage 2: Transfer sent from Ethereum to Sui gets queued
|
|
numTrans, netValueTrans, numPending, valuePending = gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 2, len(gov.msgsSeen)) // Two messages observed
|
|
assert.Equal(t, 2, numTrans) // Two transfers (same as previous step)
|
|
assert.Equal(t, int64(0), netValueTrans) // The two transfers and their inverses cancel each other out.
|
|
assert.Equal(t, 1, numPending) // Second transfer is queued because the limit is exhausted
|
|
assert.Equal(t, uint64(500), valuePending)
|
|
|
|
// Check the state of the governor.
|
|
chainEntryEthereum = gov.chains[vaa.ChainIDEthereum]
|
|
chainEntrySui = gov.chains[vaa.ChainIDSui]
|
|
assert.Equal(t, int(1), len(chainEntryEthereum.transfers)) // One from previous step
|
|
assert.Equal(t, int(1), len(chainEntryEthereum.pending)) // One for inbound refund and another for outbound
|
|
assert.Equal(t, int(1), len(chainEntrySui.transfers)) // One inverse transfer. Inverse from pending not added yet
|
|
sumEth, ethTransfers, err = gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(10000), sumEth) // Same as before: full dailyLimit
|
|
assert.Equal(t, int(1), len(ethTransfers)) // Same as before
|
|
require.NoError(t, err)
|
|
sumSui, suiTransfers, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int(1), len(suiTransfers)) // just the inverse from before
|
|
assert.Equal(t, int64(-10000), sumSui) // Unchanged.
|
|
require.NoError(t, err)
|
|
suiGovernorUsage, err = gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, suiGovernorUsage) // Actual governor usage must not be negative.
|
|
require.NoError(t, err)
|
|
|
|
// Stage 3: Message that reduces Governor usage for Ethereum (Sui --> Ethereum)
|
|
result, err = gov.ProcessMsgForTime(&msg3, time.Now())
|
|
assert.True(t, result)
|
|
require.NoError(t, err)
|
|
|
|
// Stage 3: Governor usage reduced on Ethereum due to incoming from Sui
|
|
numTrans, netValueTrans, numPending, valuePending = gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 3, len(gov.msgsSeen))
|
|
assert.Equal(t, 4, numTrans) // Two transfers and their inverses
|
|
assert.Equal(t, int64(0), netValueTrans) // Still zero because everything flow cancels
|
|
assert.Equal(t, 1, numPending) // Not released yet
|
|
assert.Equal(t, uint64(500), valuePending)
|
|
|
|
// Check the state of the governor
|
|
chainEntryEthereum = gov.chains[vaa.ChainIDEthereum]
|
|
chainEntrySui = gov.chains[vaa.ChainIDSui]
|
|
assert.Equal(t, int(2), len(chainEntryEthereum.transfers))
|
|
assert.Equal(t, int(1), len(chainEntryEthereum.pending)) // We have not yet released the pending transfer
|
|
assert.Equal(t, int(2), len(chainEntrySui.transfers))
|
|
sumEth, ethTransfers, err = gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(9000), sumEth) // We freed up room because of Sui incoming
|
|
assert.Equal(t, int(2), len(ethTransfers)) // Two transfers cancel each other out
|
|
require.NoError(t, err)
|
|
sumSui, suiTransfers, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int(2), len(suiTransfers))
|
|
assert.Equal(t, int64(-9000), sumSui) // We consumed some outbound capacity
|
|
require.NoError(t, err)
|
|
suiGovernorUsage, err = gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, uint64(0), suiGovernorUsage) // Still zero because it's still negative
|
|
require.NoError(t, err)
|
|
|
|
// Stage 4: Release the pending transfer. We deliberately do not advance the time here because we are relying
|
|
// on the pending transfer being released as a result of flow-cancelling and not because 24 hours have passed.
|
|
// NOTE that even though the function says "Checked..." it modifies `gov` as a side-effect when a pending
|
|
// transfer is ready to be released
|
|
toBePublished, err := gov.CheckPendingForTime(time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(toBePublished))
|
|
|
|
// Stage 4: Pending transfer released. This increases the Ethereum Governor usage again and reduces Sui.
|
|
numTrans, netValueTrans, numPending, valuePending = gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 3, len(gov.msgsSeen))
|
|
assert.Equal(t, 6, numTrans) // Two new transfers created from previous pending transfer
|
|
assert.Equal(t, int64(0), netValueTrans) // Still zero because everything flow cancels
|
|
assert.Equal(t, 0, numPending) // Pending transfer has been released
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
|
|
// Verify the stats that are non flow-cancelling.
|
|
// In practice this is the sum of the absolute value of all the transfers, including the inverses.
|
|
// 2 * (10000 + 1000 + 500) = 23000
|
|
_, absValueTrans, _, _ := gov.getStatsForAllChains()
|
|
assert.Equal(t, uint64(23000), absValueTrans)
|
|
|
|
// Check the state of the governor
|
|
chainEntryEthereum = gov.chains[vaa.ChainIDEthereum]
|
|
chainEntrySui = gov.chains[vaa.ChainIDSui]
|
|
assert.Equal(t, int(3), len(chainEntryEthereum.transfers)) // Two outbound, one inverse from Sui
|
|
assert.Equal(t, int(0), len(chainEntryEthereum.pending)) // Released
|
|
assert.Equal(t, int(3), len(chainEntrySui.transfers)) // One outbound, two inverses from Ethereum
|
|
sumEth, ethTransfers, err = gov.TrimAndSumValue(chainEntryEthereum.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(9500), sumEth)
|
|
assert.Equal(t, int(3), len(ethTransfers))
|
|
require.NoError(t, err)
|
|
sumSui, suiTransfers, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int(3), len(suiTransfers)) // New inverse transfer added after pending transfer was released
|
|
assert.Equal(t, int64(-9500), sumSui) // Flow-cancelling inverse transfer added to Sui after released
|
|
require.NoError(t, err)
|
|
suiGovernorUsage, err = gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, uint64(0), suiGovernorUsage) // Still zero
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSmallerPendingTransfersAfterBigOneShouldGetReleased(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 1000000, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
// The first VAA should be accepted.
|
|
msg1 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
270,
|
|
),
|
|
}
|
|
|
|
now, _ := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
canPost, err := gov.ProcessMsgForTime(&msg1, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 1, numTrans)
|
|
assert.Equal(t, uint64(479147), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// And so should the second.
|
|
msg2 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
275,
|
|
),
|
|
}
|
|
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 6:00pm (CST)")
|
|
canPost, err = gov.ProcessMsgForTime(&msg2, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(479147+488020), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 2, len(gov.msgsSeen))
|
|
|
|
// But the third, big one should be queued up.
|
|
msg3 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
500,
|
|
),
|
|
}
|
|
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 2:00am (CST)")
|
|
canPost, err = gov.ProcessMsgForTime(&msg3, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(479147+488020), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(887309), valuePending)
|
|
assert.Equal(t, 3, len(gov.msgsSeen))
|
|
|
|
// A fourth, smaller, but still too big one, should get enqueued.
|
|
msg4 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
100,
|
|
),
|
|
}
|
|
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 8:00am (CST)")
|
|
canPost, err = gov.ProcessMsgForTime(&msg4, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(479147+488020), valueTrans)
|
|
assert.Equal(t, 2, numPending)
|
|
assert.Equal(t, uint64(887309+177461), valuePending)
|
|
assert.Equal(t, 4, len(gov.msgsSeen))
|
|
|
|
// A fifth, smaller, but still too big one, should also get enqueued.
|
|
msg5 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
101,
|
|
),
|
|
}
|
|
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 8:00am (CST)")
|
|
canPost, err = gov.ProcessMsgForTime(&msg5, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(479147+488020), valueTrans)
|
|
assert.Equal(t, 3, numPending)
|
|
assert.Equal(t, uint64(887309+177461+179236), valuePending)
|
|
assert.Equal(t, 5, len(gov.msgsSeen))
|
|
|
|
// A sixth, big one should also get enqueued.
|
|
msg6 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
501,
|
|
),
|
|
}
|
|
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 2:00am (CST)")
|
|
canPost, err = gov.ProcessMsgForTime(&msg6, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(479147+488020), valueTrans)
|
|
assert.Equal(t, 4, numPending)
|
|
assert.Equal(t, uint64(887309+177461+179236+889084), valuePending)
|
|
assert.Equal(t, 6, len(gov.msgsSeen))
|
|
|
|
// If we check pending before noon, nothing should happen.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 9:00am (CST)")
|
|
toBePublished, err := gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(toBePublished))
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(479147+488020), valueTrans)
|
|
assert.Equal(t, 4, numPending)
|
|
assert.Equal(t, uint64(887309+177461+179236+889084), valuePending)
|
|
assert.Equal(t, 6, len(gov.msgsSeen))
|
|
|
|
// But at 3pm, the first one should drop off. This should result in the second and third, smaller pending ones being posted, but not the two big ones.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 3:00pm (CST)")
|
|
toBePublished, err = gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, len(toBePublished))
|
|
checkTargetOnReleasedIsSet(t, toBePublished, vaa.ChainIDPolygon, toAddrStr)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3, numTrans)
|
|
assert.Equal(t, uint64(488020+177461+179236), valueTrans)
|
|
assert.Equal(t, 2, numPending)
|
|
assert.Equal(t, uint64(887309+889084), valuePending)
|
|
assert.Equal(t, 5, len(gov.msgsSeen))
|
|
}
|
|
|
|
func TestMainnetConfigIsValid(t *testing.T) {
|
|
logger := zap.NewNop()
|
|
var db db.MockGovernorDB
|
|
gov := NewChainGovernor(logger, &db, common.GoTest, true)
|
|
|
|
gov.env = common.TestNet
|
|
err := gov.initConfig()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestTestnetConfigIsValid(t *testing.T) {
|
|
logger := zap.NewNop()
|
|
var db db.MockGovernorDB
|
|
gov := NewChainGovernor(logger, &db, common.GoTest, true)
|
|
|
|
gov.env = common.TestNet
|
|
err := gov.initConfig()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestNumDaysForReleaseTimerReset(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 1000000, 100000)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
now := time.Now()
|
|
messageTimestamp := now.Add(-5) // 5 seconds ago
|
|
|
|
// message that, when processed, should exceed the big transfer size
|
|
msg := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: messageTimestamp,
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(3),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
100,
|
|
),
|
|
}
|
|
|
|
canPost, err := gov.ProcessMsgForTime(&msg, now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, false, canPost)
|
|
|
|
msg.MessageIDString()
|
|
|
|
// check that the enqueued vaa's release date is now + 1 day
|
|
expectedReleaseTime := uint32(now.Add(24 * time.Hour).Unix())
|
|
enqueuedVaas := gov.GetEnqueuedVAAs()
|
|
assert.Equal(t, len(enqueuedVaas), 1)
|
|
assert.Equal(t, enqueuedVaas[0].ReleaseTime, expectedReleaseTime)
|
|
|
|
// the release timer gets reset to 5 days
|
|
_, err = gov.resetReleaseTimerForTime(msg.MessageIDString(), now, 5)
|
|
require.NoError(t, err)
|
|
|
|
// check that the enqueued vaa's release date is now + 5 days
|
|
enqueuedVaas = gov.GetEnqueuedVAAs()
|
|
assert.Equal(t, len(enqueuedVaas), 1)
|
|
expectedReleaseTime = uint32(now.Add(5 * 24 * time.Hour).Unix())
|
|
assert.Equal(t, enqueuedVaas[0].ReleaseTime, expectedReleaseTime)
|
|
|
|
}
|
|
|
|
func TestLargeTransactionGetsEnqueuedAndReleasedWhenTheTimerExpires(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 1000000, 100000)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
// The first small transfer should be accepted.
|
|
msg1 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
50,
|
|
),
|
|
}
|
|
|
|
now, _ := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
canPost, err := gov.ProcessMsgForTime(&msg1, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 1, numTrans)
|
|
assert.Equal(t, uint64(88730), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// And so should the second.
|
|
msg2 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(2),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
50,
|
|
),
|
|
}
|
|
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 6:00pm (CST)")
|
|
canPost, err = gov.ProcessMsgForTime(&msg2, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(88730+88730), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 2, len(gov.msgsSeen))
|
|
|
|
// But the third big one should get enqueued.
|
|
msg3 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(3),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
100,
|
|
),
|
|
}
|
|
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 2:00am (CST)")
|
|
canPost, err = gov.ProcessMsgForTime(&msg3, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(88730+88730), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(177461), valuePending)
|
|
assert.Equal(t, 3, len(gov.msgsSeen))
|
|
|
|
// If we check pending before noon, nothing should happen.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 9:00am (CST)")
|
|
toBePublished, err := gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(toBePublished))
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(88730+88730), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(177461), valuePending)
|
|
assert.Equal(t, 3, len(gov.msgsSeen))
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(88730+88730), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(177461), valuePending)
|
|
|
|
// But just after noon, the first one should drop off. The big pending one should not be affected.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 12:01pm (CST)")
|
|
toBePublished, err = gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(toBePublished))
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, numTrans)
|
|
assert.Equal(t, uint64(88730), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(177461), valuePending)
|
|
assert.Equal(t, 2, len(gov.msgsSeen))
|
|
|
|
// And Just after 6pm, the second one should drop off. The big pending one should still not be affected.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 6:01pm (CST)")
|
|
toBePublished, err = gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(toBePublished))
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(177461), valuePending)
|
|
|
|
// 23 hours after the big transaction is enqueued, it should still be there.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 3, 2022 at 1:01am (CST)")
|
|
toBePublished, err = gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(toBePublished))
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(177461), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// But then the operator resets the release time.
|
|
_, err = gov.resetReleaseTimerForTime(msg3.MessageIDString(), now, 1)
|
|
require.NoError(t, err)
|
|
|
|
// So now, 12 hours later the big transaction is enqueued, it still won't get released.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 3, 2022 at 1:00pm (CST)")
|
|
toBePublished, err = gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(toBePublished))
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(177461), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// But finally, a full 24hrs, it should get released.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 4, 2022 at 1:01am (CST)")
|
|
toBePublished, err = gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(toBePublished))
|
|
checkTargetOnReleasedIsSet(t, toBePublished, vaa.ChainIDPolygon, toAddrStr)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 0, len(gov.msgsSeen))
|
|
|
|
// But the big transaction should not affect the daily notional.
|
|
ce, exists := gov.chains[vaa.ChainIDEthereum]
|
|
require.Equal(t, true, exists)
|
|
_, outgoing, _, err := sumValue(ce.transfers, now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint64(0), outgoing)
|
|
}
|
|
|
|
func TestSmallTransactionsGetReleasedWhenTheTimerExpires(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
|
|
// This configuration does not make sense for real, but allows for this test.
|
|
// We are setting the big transfer size smaller than the daily limit, so we can
|
|
// easily enqueue a transfer that is not considered big and confirm that it eventually
|
|
// gets released after the release time passes.
|
|
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 10000, 100000)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
// Submit a small transfer that will get enqueued due to the low daily limit.
|
|
msg1 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
50,
|
|
),
|
|
}
|
|
|
|
now, _ := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
canPost, err := gov.ProcessMsgForTime(&msg1, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(88730), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// If we check 23hrs later, nothing should happen.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 11:00am (CST)")
|
|
toBePublished, err := gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(toBePublished))
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(88730), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// But after 24hrs, it should get released.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 12:01pm (CST)")
|
|
toBePublished, err = gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(toBePublished))
|
|
checkTargetOnReleasedIsSet(t, toBePublished, vaa.ChainIDPolygon, toAddrStr)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 0, len(gov.msgsSeen))
|
|
}
|
|
|
|
func TestIsBigTransfer(t *testing.T) {
|
|
emitterAddr := vaa.Address{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}
|
|
bigTransactionSize := uint64(5_000_000)
|
|
|
|
ce := chainEntry{
|
|
emitterChainId: vaa.ChainIDEthereum,
|
|
emitterAddr: emitterAddr,
|
|
dailyLimit: uint64(50_000_000),
|
|
bigTransactionSize: bigTransactionSize,
|
|
checkForBigTransactions: bigTransactionSize != 0,
|
|
}
|
|
|
|
assert.Equal(t, false, ce.isBigTransfer(uint64(4_999_999)))
|
|
assert.Equal(t, true, ce.isBigTransfer(uint64(5_000_000)))
|
|
assert.Equal(t, true, ce.isBigTransfer(uint64(5_000_001)))
|
|
}
|
|
|
|
func TestTransferPayloadTooShort(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 1000000, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
payloadBytes1 := buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
1.25,
|
|
)
|
|
|
|
payloadBytes1 = payloadBytes1[0 : len(payloadBytes1)-1]
|
|
|
|
msg := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payloadBytes1,
|
|
}
|
|
|
|
// The low level method should return an error.
|
|
_, err = gov.ProcessMsgForTime(&msg, time.Now())
|
|
assert.EqualError(t, err, "buffer too short")
|
|
assert.Equal(t, 0, len(gov.msgsSeen))
|
|
|
|
// The higher level method should return false, saying we should not publish.
|
|
canPost := gov.ProcessMsg(&msg)
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 0, len(gov.msgsSeen))
|
|
}
|
|
|
|
func TestDontReloadDuplicates(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
emitterAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
emitterAddr, err := vaa.StringToAddress(emitterAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
tokenAddr, err := vaa.StringToAddress(tokenAddrStr)
|
|
require.NoError(t, err)
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, emitterAddrStr, 1000000, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, emitterAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
now, _ := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 12:01pm (CST)")
|
|
startTime := now.Add(-time.Minute * time.Duration(gov.dayLengthInMinutes))
|
|
|
|
var xfers []*db.Transfer
|
|
|
|
xfer1 := &db.Transfer{
|
|
Timestamp: startTime.Add(time.Minute * 5),
|
|
Value: uint64(1000),
|
|
OriginChain: vaa.ChainIDEthereum,
|
|
OriginAddress: tokenAddr,
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: emitterAddr,
|
|
MsgID: "2/" + emitterAddrStr + "/125",
|
|
Hash: "Hash1",
|
|
}
|
|
xfers = append(xfers, xfer1)
|
|
|
|
xfer2 := &db.Transfer{
|
|
Timestamp: startTime.Add(time.Minute * 5),
|
|
Value: uint64(2000),
|
|
OriginChain: vaa.ChainIDEthereum,
|
|
OriginAddress: tokenAddr,
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: emitterAddr,
|
|
MsgID: "2/" + emitterAddrStr + "/126",
|
|
Hash: "Hash2",
|
|
}
|
|
xfers = append(xfers, xfer2)
|
|
|
|
// Add a duplicate of each transfer
|
|
xfers = append(xfers, xfer1)
|
|
xfers = append(xfers, xfer2)
|
|
assert.Equal(t, 4, len(xfers))
|
|
|
|
payload1 := buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
1.25,
|
|
)
|
|
|
|
var pendings []*db.PendingTransfer
|
|
pending1 := &db.PendingTransfer{
|
|
ReleaseTime: now.Add(time.Hour * 24),
|
|
Msg: common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(200),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: emitterAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payload1,
|
|
},
|
|
}
|
|
pendings = append(pendings, pending1)
|
|
|
|
pending2 := &db.PendingTransfer{
|
|
ReleaseTime: now.Add(time.Hour * 24),
|
|
Msg: common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(201),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: emitterAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: payload1,
|
|
},
|
|
}
|
|
pendings = append(pendings, pending2)
|
|
|
|
// Add a duplicate of each pending transfer
|
|
pendings = append(pendings, pending1)
|
|
pendings = append(pendings, pending2)
|
|
assert.Equal(t, 4, len(pendings))
|
|
|
|
for _, p := range xfers {
|
|
err := gov.reloadTransfer(p)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
for _, p := range pendings {
|
|
gov.reloadPendingTransfer(p)
|
|
}
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(3000), valueTrans)
|
|
assert.Equal(t, 2, numPending)
|
|
assert.Equal(t, uint64(4436), valuePending)
|
|
}
|
|
|
|
// With the addition of the flow-cancel feature, it's possible to in a way "exceed the daily limit" of outflow from a
|
|
// Governor as long as there is corresponding inflow of a flow-canceling asset to allow for additional outflow.
|
|
// When the node is restarted, it reloads all transfers and pending transfers. If the actual outflow is greater than
|
|
// the daily limit (due to flow cancel) ensure that the calculated limit on start-up is correct.
|
|
// This test ensures that governor usage limits are correctly calculated when reloading transfers from the database.
|
|
func TestReloadTransfersNearCapacity(t *testing.T) {
|
|
// Setup
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
// Set-up time
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
transferTime := time.Now()
|
|
|
|
// Solana USDC used as the flow cancelling asset. This ensures that the flow cancel mechanism works
|
|
// when the Origin chain of the asset does not match the emitter chain
|
|
// NOTE: Replace this Chain:Address pair if the Flow Cancel Token List is modified
|
|
var flowCancelTokenOriginAddress vaa.Address
|
|
flowCancelTokenOriginAddress, err = vaa.StringToAddress("c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61")
|
|
require.NoError(t, err)
|
|
|
|
var notFlowCancelTokenOriginAddress vaa.Address
|
|
notFlowCancelTokenOriginAddress, err = vaa.StringToAddress("77777af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f7777")
|
|
require.NoError(t, err)
|
|
|
|
// Data for Ethereum
|
|
tokenBridgeAddrStrEthereum := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddrEthereum, err := vaa.StringToAddress(tokenBridgeAddrStrEthereum)
|
|
require.NoError(t, err)
|
|
|
|
// Data for Sui
|
|
tokenBridgeAddrStrSui := "0xc57508ee0d4595e5a8728974a4a93a787d38f339757230d441e895422c07aba9" //nolint:gosec
|
|
tokenBridgeAddrSui, err := vaa.StringToAddress(tokenBridgeAddrStrSui)
|
|
require.NoError(t, err)
|
|
|
|
// Data for Solana. Only used to represent the flow cancel asset.
|
|
// "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb"
|
|
tokenBridgeAddrStrSolana := "0x0e0a589e6488147a94dcfa592b90fdd41152bb2ca77bf6016758a6f4df9d21b4" //nolint:gosec
|
|
|
|
// Add chain entries to `gov`
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStrEthereum, 10000, 50000)
|
|
require.NoError(t, err)
|
|
err = gov.setChainForTesting(vaa.ChainIDSui, tokenBridgeAddrStrSui, 10000, 0)
|
|
require.NoError(t, err)
|
|
err = gov.setChainForTesting(vaa.ChainIDSolana, tokenBridgeAddrStrSolana, 10000, 0)
|
|
require.NoError(t, err)
|
|
|
|
// Add flow cancel asset and non-flow cancelable asset to the token entry for `gov`
|
|
err = gov.setTokenForTesting(vaa.ChainIDSolana, flowCancelTokenOriginAddress.String(), "USDC", 1.0, true)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov.tokens[tokenKey{chain: vaa.ChainIDSolana, addr: flowCancelTokenOriginAddress}])
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, notFlowCancelTokenOriginAddress.String(), "NOTCANCELABLE", 1.0, false)
|
|
require.NoError(t, err)
|
|
|
|
// This transfer should exhaust the dailyLimit for the emitter chain
|
|
xfer1 := &db.Transfer{
|
|
Timestamp: transferTime.Add(-10),
|
|
Value: uint64(10000),
|
|
OriginChain: vaa.ChainIDSolana,
|
|
OriginAddress: flowCancelTokenOriginAddress,
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddrEthereum,
|
|
TargetAddress: tokenBridgeAddrSui,
|
|
TargetChain: vaa.ChainIDSui,
|
|
MsgID: "2/" + tokenBridgeAddrEthereum.String() + "/125",
|
|
Hash: "Hash1",
|
|
}
|
|
|
|
// This incoming transfer should free up some of the space on the previous emitter chain
|
|
xfer2 := &db.Transfer{
|
|
Timestamp: transferTime.Add(-9),
|
|
Value: uint64(2000),
|
|
OriginChain: vaa.ChainIDSolana,
|
|
OriginAddress: flowCancelTokenOriginAddress,
|
|
EmitterChain: vaa.ChainIDSui,
|
|
EmitterAddress: tokenBridgeAddrSui,
|
|
TargetAddress: tokenBridgeAddrEthereum,
|
|
TargetChain: vaa.ChainIDEthereum,
|
|
MsgID: "2/" + tokenBridgeAddrSui.String() + "/126",
|
|
Hash: "Hash2",
|
|
}
|
|
|
|
// Send another transfer out from the original emitter chain so that we "exceed the daily limit" if flow
|
|
// cancel is not applied
|
|
xfer3 := &db.Transfer{
|
|
Timestamp: transferTime.Add(-8),
|
|
Value: uint64(50),
|
|
OriginChain: vaa.ChainIDSolana,
|
|
OriginAddress: flowCancelTokenOriginAddress,
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddrEthereum,
|
|
TargetAddress: tokenBridgeAddrSui,
|
|
TargetChain: vaa.ChainIDSui,
|
|
MsgID: "2/" + tokenBridgeAddrEthereum.String() + "/125",
|
|
Hash: "Hash3",
|
|
}
|
|
|
|
// Simulate reloading from the database.
|
|
// NOTE: The actual execution path we want to test is the following and runs when the node is restarted:
|
|
// gov.Run () --> gov.loadFromDb() --> gov.loadFromDBAlreadyLocked() --> gov.reloadTransfer()
|
|
// We don't have access to Run() from the test suite and the other functions are mocked to return `nil`.
|
|
// Therefore, the remainder of this test proceeds by operating on a list of `transfersLoadedFromDb` which
|
|
// simulates loading transfers from the database.
|
|
// From here we proceed with the next function we can actually test: `reloadTransfer()`.
|
|
|
|
// STEP 0: Initial state
|
|
assert.Equal(t, len(gov.msgsSeen), 0)
|
|
numTrans, netValueTransferred, numPending, valuePending := gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, int64(0), netValueTransferred)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
|
|
chainEntryEth, exists := gov.chains[vaa.ChainIDEthereum]
|
|
require.True(t, exists)
|
|
chainEntrySui, exists := gov.chains[vaa.ChainIDSui]
|
|
require.True(t, exists)
|
|
|
|
// STEP 1: Load first transfer
|
|
err = gov.reloadTransfer(xfer1)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, len(gov.msgsSeen), 1)
|
|
numTrans, netValueTransferred, _, _ = gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 2, numTrans) // 1 plus transfer the inverse flow transfer on the TargetChain
|
|
assert.Equal(t, int64(0), netValueTransferred) // Value cancels out for all transfers
|
|
|
|
// Sum of absolute value of all transfers, including inverse flow cancel transfers:
|
|
// 2 * (10_000) = 20_000
|
|
_, valueTransferred, _, _ := gov.getStatsForAllChains()
|
|
assert.Equal(t, uint64(20000), valueTransferred)
|
|
|
|
governorUsageEth, err := gov.TrimAndSumValueForChain(chainEntryEth, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, uint64(10000), governorUsageEth)
|
|
assert.Zero(t, governorUsageEth-chainEntryEth.dailyLimit) // Make sure we used the whole capacity
|
|
require.NoError(t, err)
|
|
governorUsageSui, err := gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, governorUsageSui)
|
|
require.NoError(t, err)
|
|
sumTransfersSui, _, err := gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(-10000), sumTransfersSui)
|
|
require.NoError(t, err)
|
|
|
|
// STEP 2: Load second transfer
|
|
err = gov.reloadTransfer(xfer2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, len(gov.msgsSeen), 2)
|
|
numTrans, netValueTransferred, _, _ = gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 4, numTrans) // 2 transfers and their inverse flow transfers on the TargetChain
|
|
assert.Equal(t, int64(0), netValueTransferred) // Value cancels out for all transfers
|
|
|
|
// Sum of absolute value of all transfers, including inverse flow cancel transfers:
|
|
// 2 * (10_000 + 2_000) = 24_000
|
|
_, valueTransferred, _, _ = gov.getStatsForAllChains()
|
|
assert.Equal(t, uint64(24000), valueTransferred)
|
|
|
|
governorUsageEth, err = gov.TrimAndSumValueForChain(chainEntryEth, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, uint64(8000), governorUsageEth)
|
|
assert.Equal(t, int(chainEntryEth.dailyLimit-governorUsageEth), 2000) // Remaining capacity
|
|
require.NoError(t, err)
|
|
governorUsageSui, err = gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Zero(t, governorUsageSui)
|
|
require.NoError(t, err)
|
|
sumTransfersSui, _, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(-8000), sumTransfersSui)
|
|
require.NoError(t, err)
|
|
|
|
// STEP 3: Load third transfer
|
|
err = gov.reloadTransfer(xfer3)
|
|
require.NoError(t, err)
|
|
// Sum of absolute value of all transfers, including inverse flow cancel transfers:
|
|
// 2 * (10_000 + 2_000 + 50) = 24_100
|
|
_, valueTransferred, _, _ = gov.getStatsForAllChains()
|
|
assert.Equal(t, uint64(24100), valueTransferred)
|
|
|
|
numTrans, netValueTransferred, numPending, valuePending = gov.getStatsForAllChainsCancelFlow()
|
|
assert.Equal(t, 6, numTrans) // 3 transfers and their inverse flow transfers on the TargetChain
|
|
assert.Equal(t, int64(0), netValueTransferred) // Value cancels out for all transfers
|
|
|
|
governorUsageEth, err = gov.TrimAndSumValueForChain(chainEntryEth, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, uint64(8050), governorUsageEth)
|
|
assert.Equal(t, int(chainEntryEth.dailyLimit-governorUsageEth), 1950) // Remaining capacity
|
|
require.NoError(t, err)
|
|
governorUsageSui, err = gov.TrimAndSumValueForChain(chainEntrySui, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
require.NoError(t, err)
|
|
assert.Zero(t, governorUsageSui)
|
|
sumTransfersSui, _, err = gov.TrimAndSumValue(chainEntrySui.transfers, time.Unix(int64(transferTime.Unix()-1000), 0))
|
|
assert.Equal(t, int64(-8050), sumTransfersSui)
|
|
require.NoError(t, err)
|
|
|
|
// Sanity check: make sure these are still empty/zero
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
}
|
|
|
|
func TestReobservationOfPublishedMsg(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 1000000, 100000)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
// The first transfer should be accepted.
|
|
msg := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
50,
|
|
),
|
|
}
|
|
|
|
now, _ := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:10pm (CST)")
|
|
canPost, err := gov.ProcessMsgForTime(&msg, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 1, numTrans)
|
|
assert.Equal(t, uint64(88730), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// A reobservation of the same message should get published but should not affect the notional value.
|
|
canPost, err = gov.ProcessMsgForTime(&msg, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 1, numTrans)
|
|
assert.Equal(t, uint64(88730), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
}
|
|
|
|
func TestReobservationOfEnqueued(t *testing.T) {
|
|
// The duplicate should not get published and not get enqueued again.
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 1000000, 100000)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
// A big transfer should get enqueued.
|
|
msg := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
5000,
|
|
),
|
|
}
|
|
|
|
now, _ := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:10pm (CST)")
|
|
canPost, err := gov.ProcessMsgForTime(&msg, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(8_873_099), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// A reobservation of the same message should not get published and should not get enqueued again.
|
|
canPost, err = gov.ProcessMsgForTime(&msg, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, false, canPost)
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, uint64(0), valueTrans)
|
|
assert.Equal(t, 1, numPending)
|
|
assert.Equal(t, uint64(8_873_099), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
}
|
|
|
|
func TestReusedMsgIdWithDifferentPayloadGetsProcessed(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov, err := newChainGovernorForTest(ctx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, gov)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 1000000, 100000)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
// The first transfer should be accepted.
|
|
msg1 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
50,
|
|
),
|
|
}
|
|
|
|
now, _ := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:10pm (CST)")
|
|
canPost, err := gov.ProcessMsgForTime(&msg1, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending := gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 1, numTrans)
|
|
assert.Equal(t, uint64(88730), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 1, len(gov.msgsSeen))
|
|
|
|
// A second message with the same msgId but a different payload should also get published and apply to the notional value.
|
|
msg2 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
5,
|
|
),
|
|
}
|
|
|
|
canPost, err = gov.ProcessMsgForTime(&msg2, now)
|
|
require.NoError(t, err)
|
|
|
|
numTrans, valueTrans, numPending, valuePending = gov.getStatsForAllChains()
|
|
assert.Equal(t, true, canPost)
|
|
assert.Equal(t, 2, numTrans)
|
|
assert.Equal(t, uint64(97603), valueTrans)
|
|
assert.Equal(t, 0, numPending)
|
|
assert.Equal(t, uint64(0), valuePending)
|
|
assert.Equal(t, 2, len(gov.msgsSeen))
|
|
}
|
|
|
|
func getIdsFromCoinGeckoQuery(t *testing.T, query string) []string {
|
|
unescaped, err := url.QueryUnescape(query)
|
|
require.NoError(t, err)
|
|
|
|
fields := strings.Split(unescaped, "?")
|
|
require.Equal(t, 2, len(fields))
|
|
|
|
u, err := url.ParseQuery(fields[1])
|
|
require.NoError(t, err)
|
|
|
|
idField, exists := u["ids"]
|
|
require.Equal(t, true, exists)
|
|
require.Equal(t, 1, len(idField))
|
|
|
|
return strings.Split(idField[0], ",")
|
|
}
|
|
|
|
func TestCoinGeckoQueries(t *testing.T) {
|
|
type testCase struct {
|
|
desc string
|
|
numIds int
|
|
chunkSize int
|
|
expectedQueries int
|
|
}
|
|
|
|
tests := []testCase{
|
|
{numIds: 0, chunkSize: 100, expectedQueries: 0, desc: "Zero queries"},
|
|
{numIds: 42, chunkSize: 100, expectedQueries: 1, desc: "Easily fits in one"},
|
|
{numIds: 100, chunkSize: 100, expectedQueries: 1, desc: "Exactly fits in one"},
|
|
{numIds: 242, chunkSize: 207, expectedQueries: 2, desc: "Easily fits in two"},
|
|
{numIds: 414, chunkSize: 207, expectedQueries: 2, desc: "Exactly fits in two"},
|
|
{numIds: 5001, chunkSize: 207, expectedQueries: 25, desc: "A bunch of queries"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
ids := make([]string, tc.numIds)
|
|
for idx := 0; idx < tc.numIds; idx++ {
|
|
ids[idx] = fmt.Sprintf("id%d", idx)
|
|
}
|
|
|
|
queries := createCoinGeckoQueries(ids, tc.chunkSize)
|
|
require.Equal(t, tc.expectedQueries, len(queries))
|
|
|
|
results := make(map[string]string)
|
|
for _, query := range queries {
|
|
idsInQuery := getIdsFromCoinGeckoQuery(t, query)
|
|
require.GreaterOrEqual(t, tc.chunkSize, len(idsInQuery))
|
|
for _, id := range idsInQuery {
|
|
results[id] = id
|
|
}
|
|
}
|
|
|
|
require.Equal(t, tc.numIds, len(results))
|
|
|
|
for _, id := range ids {
|
|
if _, exists := results[id]; !exists {
|
|
assert.Equal(t, "id not found in query", id)
|
|
}
|
|
delete(results, id)
|
|
}
|
|
if len(results) != 0 {
|
|
for id := range results {
|
|
assert.Equal(t, "bogus id created by query", id)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// setupLogsCapture is a helper function for making a zap logger/observer combination for testing that certain logs have been made
|
|
func setupLogsCapture(t testing.TB, options ...zap.Option) (*zap.Logger, *observer.ObservedLogs) {
|
|
t.Helper()
|
|
observedCore, observedLogs := observer.New(zap.InfoLevel)
|
|
consoleLogger := zaptest.NewLogger(t, zaptest.Level(zap.InfoLevel))
|
|
parentLogger := zap.New(zapcore.NewTee(observedCore, consoleLogger.Core()), options...)
|
|
return parentLogger, observedLogs
|
|
}
|
|
|
|
func TestPendingTransferWithBadPayloadGetsDroppedNotReleased(t *testing.T) {
|
|
ctx := context.Background()
|
|
zapLogger, zapObserver := setupLogsCapture(t)
|
|
gov, err := newChainGovernorForTestWithLogger(ctx, zapLogger)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, gov)
|
|
|
|
tokenAddrStr := "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" //nolint:gosec
|
|
toAddrStr := "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8"
|
|
tokenBridgeAddrStr := "0x0290fb167208af455bb137780163b7b7a9a10c16" //nolint:gosec
|
|
tokenBridgeAddr, err := vaa.StringToAddress(tokenBridgeAddrStr)
|
|
require.NoError(t, err)
|
|
|
|
gov.setDayLengthInMinutes(24 * 60)
|
|
|
|
err = gov.setChainForTesting(vaa.ChainIDEthereum, tokenBridgeAddrStr, 10000, 100000)
|
|
require.NoError(t, err)
|
|
err = gov.setTokenForTesting(vaa.ChainIDEthereum, tokenAddrStr, "WETH", 1774.62, false)
|
|
require.NoError(t, err)
|
|
|
|
// Create two big transactions.
|
|
msg1 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(1),
|
|
Sequence: uint64(1),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
5000,
|
|
),
|
|
}
|
|
|
|
msg2 := common.MessagePublication{
|
|
TxHash: hashFromString("0x06f541f5ecfc43407c31587aa6ac3a689e8960f36dc23c332db5510dfc6a4063"),
|
|
Timestamp: time.Unix(int64(1654543099), 0),
|
|
Nonce: uint32(2),
|
|
Sequence: uint64(2),
|
|
EmitterChain: vaa.ChainIDEthereum,
|
|
EmitterAddress: tokenBridgeAddr,
|
|
ConsistencyLevel: uint8(32),
|
|
Payload: buildMockTransferPayloadBytes(1,
|
|
vaa.ChainIDEthereum,
|
|
tokenAddrStr,
|
|
vaa.ChainIDPolygon,
|
|
toAddrStr,
|
|
5000,
|
|
),
|
|
}
|
|
|
|
// Post the two big transfers and and verify they get enqueued.
|
|
now, _ := time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 1, 2022 at 12:00pm (CST)")
|
|
canPost, err := gov.ProcessMsgForTime(&msg1, now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, false, canPost)
|
|
|
|
canPost, err = gov.ProcessMsgForTime(&msg2, now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, false, canPost)
|
|
|
|
numTrans, _, numPending, _ := gov.getStatsForAllChains()
|
|
assert.Equal(t, 2, len(gov.msgsSeen))
|
|
assert.Equal(t, 0, numTrans)
|
|
assert.Equal(t, 2, numPending)
|
|
|
|
// Corrupt the payload of msg2 so that when we try to release it, it will get dropped.
|
|
gov.mutex.Lock()
|
|
ce, exists := gov.chains[vaa.ChainIDEthereum]
|
|
require.True(t, exists)
|
|
require.Equal(t, 2, len(ce.pending))
|
|
require.Equal(t, "2/0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16/2", ce.pending[1].dbData.Msg.MessageIDString())
|
|
ce.pending[1].dbData.Msg.Payload = nil
|
|
gov.mutex.Unlock()
|
|
|
|
// After 24hrs, msg1 should get released but msg2 should get dropped.
|
|
now, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Jun 2, 2022 at 12:01pm (CST)")
|
|
toBePublished, err := gov.CheckPendingForTime(now)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(toBePublished))
|
|
checkTargetOnReleasedIsSet(t, toBePublished, vaa.ChainIDPolygon, toAddrStr)
|
|
assert.Equal(t, "2/0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16/1", toBePublished[0].MessageIDString())
|
|
|
|
// Verify that we got the expected error in the logs.
|
|
loggedEntries := zapObserver.FilterMessage("failed to decode payload for pending VAA, dropping it").All()
|
|
require.Equal(t, 1, len(loggedEntries))
|
|
|
|
foundIt := false
|
|
for _, f := range loggedEntries[0].Context {
|
|
if f.Key == "msgID" && f.String == "2/0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16/2" {
|
|
foundIt = true
|
|
}
|
|
}
|
|
assert.True(t, foundIt)
|
|
|
|
// Verify that the message is no longer pending.
|
|
gov.mutex.Lock()
|
|
ce, exists = gov.chains[vaa.ChainIDEthereum]
|
|
require.True(t, exists)
|
|
assert.Equal(t, 0, len(ce.pending))
|
|
gov.mutex.Unlock()
|
|
|
|
// Neither one should be in the map of messages seen.
|
|
_, exists = gov.msgsSeen[gov.HashFromMsg(&msg1)]
|
|
assert.False(t, exists)
|
|
_, exists = gov.msgsSeen[gov.HashFromMsg(&msg2)]
|
|
assert.False(t, exists)
|
|
}
|
|
|
|
func TestCheckedAddUint64HappyPath(t *testing.T) {
|
|
// Both non-zero
|
|
x := uint64(1000)
|
|
y := uint64(337)
|
|
sum, err := CheckedAddUint64(x, y)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint64(1337), sum)
|
|
|
|
// x is zero
|
|
x = 0
|
|
y = 2000
|
|
sum, err = CheckedAddUint64(x, y)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint64(2000), sum)
|
|
|
|
// y is zero
|
|
x = 3000
|
|
y = 0
|
|
sum, err = CheckedAddUint64(x, y)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint64(3000), sum)
|
|
}
|
|
|
|
func TestCheckedAddInt64HappyPath(t *testing.T) {
|
|
// Two positive numbers
|
|
x := int64(1000)
|
|
y := int64(337)
|
|
sum, err := CheckedAddInt64(x, y)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1337), sum)
|
|
|
|
// One positive, one negative
|
|
x = 100
|
|
y = -1000
|
|
sum, err = CheckedAddInt64(x, y)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(-900), sum)
|
|
|
|
// Both negative
|
|
x = -100
|
|
y = -1000
|
|
sum, err = CheckedAddInt64(x, y)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(-1100), sum)
|
|
|
|
// x is zero
|
|
x = 0
|
|
y = 2000
|
|
sum, err = CheckedAddInt64(x, y)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2000), sum)
|
|
|
|
// y is zero
|
|
x = 3000
|
|
y = 0
|
|
sum, err = CheckedAddInt64(x, y)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(3000), sum)
|
|
}
|
|
|
|
func TestCheckedAddUint64ReturnsErrorOnOverflow(t *testing.T) {
|
|
// Return error on overflow
|
|
sum, err := CheckedAddUint64(math.MaxUint64, 1)
|
|
require.Error(t, err)
|
|
assert.Equal(t, uint64(0), sum)
|
|
}
|
|
|
|
func TestCheckedAddInt64ReturnsErrorOnOverflow(t *testing.T) {
|
|
// Return error on overflow
|
|
sum, err := CheckedAddInt64(math.MaxInt64, 1)
|
|
require.Error(t, err)
|
|
assert.Equal(t, int64(0), sum)
|
|
}
|
|
|
|
func TestCheckedAddInt64ReturnsErrorOnUnderflow(t *testing.T) {
|
|
// Return error on underflow
|
|
sum, err := CheckedAddInt64(math.MinInt64, -1)
|
|
require.Error(t, err)
|
|
assert.Equal(t, int64(0), sum)
|
|
}
|