services/rfq/relayer/relconfig/config_test.go
package relconfig_test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/synapsecns/sanguine/services/rfq/relayer/relconfig"
)
//nolint:maintidx
func TestChainGetters(t *testing.T) {
chainID := 1
badChainID := 2
usdcAddr := "0x123"
cfgWithBase := relconfig.Config{
Chains: map[int]relconfig.ChainConfig{
chainID: {
RFQAddress: "0x123",
Confirmations: 1,
NativeToken: "MATIC",
DeadlineBufferSeconds: 10,
OriginGasEstimate: 10000,
DestGasEstimate: 20000,
L1FeeChainID: 10,
L1FeeOriginGasEstimate: 30000,
L1FeeDestGasEstimate: 40000,
MinGasToken: "1000",
QuotePct: relconfig.NewFloatPtr(0),
QuoteFixedFeeMultiplier: relconfig.NewFloatPtr(1.1),
RebalanceConfigs: relconfig.RebalanceConfigs{
Synapse: &relconfig.SynapseCCTPRebalanceConfig{
SynapseCCTPAddress: "0x456",
},
Circle: &relconfig.CircleCCTPRebalanceConfig{
TokenMessengerAddress: "0x789",
},
Scroll: &relconfig.ScrollRebalanceConfig{
L1GatewayAddress: "0xabc",
L1ScrollMessengerAddress: "0xdef",
L2GatewayAddress: "0xghi",
},
},
},
},
BaseChainConfig: relconfig.ChainConfig{
RFQAddress: "0x1234",
Confirmations: 2,
NativeToken: "ARB",
DeadlineBufferSeconds: 11,
OriginGasEstimate: 10001,
DestGasEstimate: 20001,
L1FeeChainID: 11,
L1FeeOriginGasEstimate: 30001,
L1FeeDestGasEstimate: 40001,
MinGasToken: "1001",
QuotePct: relconfig.NewFloatPtr(51),
QuoteFixedFeeMultiplier: relconfig.NewFloatPtr(1.2),
RebalanceConfigs: relconfig.RebalanceConfigs{
Synapse: &relconfig.SynapseCCTPRebalanceConfig{
SynapseCCTPAddress: "0x456",
},
Circle: &relconfig.CircleCCTPRebalanceConfig{
TokenMessengerAddress: "0x789",
},
Scroll: &relconfig.ScrollRebalanceConfig{
L1GatewayAddress: "0xabc",
L1ScrollMessengerAddress: "0xdef",
L2GatewayAddress: "0xghi",
},
},
},
}
cfg := relconfig.Config{
Chains: map[int]relconfig.ChainConfig{
chainID: {
RFQAddress: "0x123",
Confirmations: 1,
NativeToken: "MATIC",
DeadlineBufferSeconds: 10,
OriginGasEstimate: 10000,
DestGasEstimate: 20000,
L1FeeChainID: 10,
L1FeeOriginGasEstimate: 30000,
L1FeeDestGasEstimate: 40000,
MinGasToken: "1000",
QuotePct: relconfig.NewFloatPtr(50),
QuoteFixedFeeMultiplier: relconfig.NewFloatPtr(1.1),
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
Address: usdcAddr,
Decimals: 6,
MaxRebalanceAmount: "1000",
},
},
RebalanceConfigs: relconfig.RebalanceConfigs{
Synapse: &relconfig.SynapseCCTPRebalanceConfig{
SynapseCCTPAddress: "0x456",
},
Circle: &relconfig.CircleCCTPRebalanceConfig{
TokenMessengerAddress: "0x789",
},
Scroll: &relconfig.ScrollRebalanceConfig{
L1GatewayAddress: "0xabc",
L1ScrollMessengerAddress: "0xdef",
L2GatewayAddress: "0xghi",
},
},
},
},
}
t.Run("GetConfirmations", func(t *testing.T) {
defaultVal, err := cfg.GetConfirmations(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.Confirmations)
baseVal, err := cfgWithBase.GetConfirmations(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.Confirmations)
chainVal, err := cfgWithBase.GetConfirmations(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].Confirmations)
})
t.Run("GetNativeToken", func(t *testing.T) {
defaultVal, err := cfg.GetNativeToken(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.NativeToken)
baseVal, err := cfgWithBase.GetNativeToken(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.NativeToken)
chainVal, err := cfgWithBase.GetNativeToken(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].NativeToken)
})
t.Run("GetDeadlineBuffer", func(t *testing.T) {
defaultVal, err := cfg.GetDeadlineBuffer(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal, time.Duration(relconfig.DefaultChainConfig.DeadlineBufferSeconds)*time.Second)
baseVal, err := cfgWithBase.GetDeadlineBuffer(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal, time.Duration(cfgWithBase.BaseChainConfig.DeadlineBufferSeconds)*time.Second)
chainVal, err := cfgWithBase.GetDeadlineBuffer(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal, time.Duration(cfgWithBase.Chains[chainID].DeadlineBufferSeconds)*time.Second)
})
t.Run("GetOriginGasEstimate", func(t *testing.T) {
defaultVal, err := cfg.GetOriginGasEstimate(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.OriginGasEstimate)
baseVal, err := cfgWithBase.GetOriginGasEstimate(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.OriginGasEstimate)
chainVal, err := cfgWithBase.GetOriginGasEstimate(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].OriginGasEstimate)
})
t.Run("GetDestGasEstimate", func(t *testing.T) {
defaultVal, err := cfg.GetDestGasEstimate(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.DestGasEstimate)
baseVal, err := cfgWithBase.GetDestGasEstimate(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.DestGasEstimate)
chainVal, err := cfgWithBase.GetDestGasEstimate(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].DestGasEstimate)
})
t.Run("GetL1FeeChainID", func(t *testing.T) {
defaultVal, err := cfg.GetL1FeeChainID(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.L1FeeChainID)
baseVal, err := cfgWithBase.GetL1FeeChainID(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.L1FeeChainID)
chainVal, err := cfgWithBase.GetL1FeeChainID(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].L1FeeChainID)
})
t.Run("GetL1FeeOriginGasEstimate", func(t *testing.T) {
defaultVal, err := cfg.GetL1FeeOriginGasEstimate(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.L1FeeOriginGasEstimate)
baseVal, err := cfgWithBase.GetL1FeeOriginGasEstimate(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.L1FeeOriginGasEstimate)
chainVal, err := cfgWithBase.GetL1FeeOriginGasEstimate(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].L1FeeOriginGasEstimate)
})
t.Run("GetL1FeeDestGasEstimate", func(t *testing.T) {
defaultVal, err := cfg.GetL1FeeDestGasEstimate(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.L1FeeDestGasEstimate)
baseVal, err := cfgWithBase.GetL1FeeDestGasEstimate(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.L1FeeDestGasEstimate)
chainVal, err := cfgWithBase.GetL1FeeDestGasEstimate(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].L1FeeDestGasEstimate)
})
t.Run("GetMinGasToken", func(t *testing.T) {
defaultVal, err := cfg.GetMinGasToken(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal.String(), relconfig.DefaultChainConfig.MinGasToken)
baseVal, err := cfgWithBase.GetMinGasToken(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal.String(), cfgWithBase.BaseChainConfig.MinGasToken)
chainVal, err := cfgWithBase.GetMinGasToken(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal.String(), cfgWithBase.Chains[chainID].MinGasToken)
})
t.Run("GetQuotePct", func(t *testing.T) {
defaultVal, err := cfg.GetQuotePct(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal, 100.)
baseVal, err := cfgWithBase.GetQuotePct(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal, 51.)
chainVal, err := cfgWithBase.GetQuotePct(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal, 0.)
})
t.Run("GetQuoteFixedFeeMultiplier", func(t *testing.T) {
defaultVal, err := cfg.GetQuoteFixedFeeMultiplier(badChainID)
assert.NoError(t, err)
assert.Equal(t, defaultVal, *relconfig.DefaultChainConfig.QuoteFixedFeeMultiplier)
baseVal, err := cfgWithBase.GetQuoteFixedFeeMultiplier(badChainID)
assert.NoError(t, err)
assert.Equal(t, baseVal, *cfgWithBase.BaseChainConfig.QuoteFixedFeeMultiplier)
chainVal, err := cfgWithBase.GetQuoteFixedFeeMultiplier(chainID)
assert.NoError(t, err)
assert.Equal(t, chainVal, *cfgWithBase.Chains[chainID].QuoteFixedFeeMultiplier)
})
t.Run("GetMaxRebalanceAmount", func(t *testing.T) {
defaultVal := cfg.GetMaxRebalanceAmount(badChainID, common.HexToAddress(usdcAddr))
assert.Equal(t, defaultVal.String(), abi.MaxInt256.String())
chainVal := cfg.GetMaxRebalanceAmount(chainID, common.HexToAddress(usdcAddr))
assert.Equal(t, chainVal.String(), "1000000000")
})
}
func TestGetQuoteOffset(t *testing.T) {
chainID := 1
usdcAddr := "0x123"
cfg := relconfig.Config{
Chains: map[int]relconfig.ChainConfig{
chainID: {
RFQAddress: "0x123",
Confirmations: 1,
NativeToken: "MATIC",
DeadlineBufferSeconds: 10,
OriginGasEstimate: 10000,
DestGasEstimate: 20000,
L1FeeChainID: 10,
L1FeeOriginGasEstimate: 30000,
L1FeeDestGasEstimate: 40000,
MinGasToken: "1000",
QuotePct: relconfig.NewFloatPtr(50),
QuoteFixedFeeMultiplier: relconfig.NewFloatPtr(1.1),
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
Address: usdcAddr,
Decimals: 6,
MaxRebalanceAmount: "1000",
QuoteOffsetBps: 100,
},
},
},
},
}
t.Run("GetQuoteOffsetForOrigin", func(t *testing.T) {
val, err := cfg.GetQuoteOffsetBps(chainID, "USDC", true)
assert.NoError(t, err)
assert.Equal(t, -100., val)
})
t.Run("GetQuoteOffsetForDest", func(t *testing.T) {
val, err := cfg.GetQuoteOffsetBps(chainID, "USDC", false)
assert.NoError(t, err)
assert.Equal(t, 100., val)
})
}
func TestValidation(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
cfg := relconfig.Config{
Chains: map[int]relconfig.ChainConfig{
1: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
InitialBalancePct: 50,
MaintenanceBalancePct: 25,
RebalanceMethods: []string{"synapsecctp"},
},
},
},
2: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
InitialBalancePct: 50,
MaintenanceBalancePct: 25,
RebalanceMethods: []string{"synapsecctp"},
},
},
},
},
}
err := cfg.Validate(context.Background(), nil)
assert.Nil(t, err)
})
t.Run("InvalidInitialPct", func(t *testing.T) {
cfg := relconfig.Config{
Chains: map[int]relconfig.ChainConfig{
1: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
InitialBalancePct: 51,
MaintenanceBalancePct: 50,
RebalanceMethods: []string{"synapsecctp"},
},
},
},
2: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
InitialBalancePct: 50,
MaintenanceBalancePct: 50,
RebalanceMethods: []string{"synapsecctp"},
},
},
},
},
}
err := cfg.Validate(context.Background(), nil)
assert.NotNil(t, err)
assert.Equal(t, "total initial percent does not total 100 for USDC: 101.000000", err.Error())
})
t.Run("InvalidMaintenancePct", func(t *testing.T) {
cfg := relconfig.Config{
Chains: map[int]relconfig.ChainConfig{
1: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
InitialBalancePct: 50,
MaintenanceBalancePct: 50,
RebalanceMethods: []string{"synapsecctp"},
},
},
},
2: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
InitialBalancePct: 50,
MaintenanceBalancePct: 50.1,
RebalanceMethods: []string{"synapsecctp"},
},
},
},
},
}
err := cfg.Validate(context.Background(), nil)
assert.NotNil(t, err)
assert.Equal(t, "total maintenance percent exceeds 100 for USDC: 100.100000", err.Error())
})
t.Run("ValidWithNoRebalanceMethod", func(t *testing.T) {
cfg := relconfig.Config{
Chains: map[int]relconfig.ChainConfig{
1: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
InitialBalancePct: 50,
MaintenanceBalancePct: 20,
},
},
},
2: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
InitialBalancePct: 50,
MaintenanceBalancePct: 20,
},
},
},
},
}
err := cfg.Validate(context.Background(), nil)
assert.Nil(t, err)
})
}
func TestDecodeTokenID(t *testing.T) {
tests := []struct {
name string
id string
wantChain int
wantAddr common.Address
wantErr bool
}{
{
name: "valid token ID",
id: "1-0x1234567890abcdef1234567890abcdef12345678",
wantChain: 1,
wantAddr: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"),
wantErr: false,
},
{
name: "invalid token ID format",
id: "1_0x1234567890abcdef1234567890abcdef12345678",
wantErr: true,
},
{
name: "invalid chain ID",
id: "x-0x1234567890abcdef1234567890abcdef12345678",
wantErr: true,
},
{
name: "invalid address",
id: "1-0x12345",
wantErr: true,
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
gotChain, gotAddr, err := relconfig.DecodeTokenID(tt.id)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.wantChain, gotChain)
assert.Equal(t, tt.wantAddr, gotAddr)
}
})
}
}
const usdcAddr = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
const arbAddr = "0x912CE59144191C1204E64559FE8253a0e49E6548"
const opAddr = "0x4200000000000000000000000000000000000042"
func (v *ValidateDecimalsSuite) TestValidateWrongDecimals() {
cfg := relconfig.Config{
Chains: map[int]relconfig.ChainConfig{
1: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
Address: usdcAddr,
Decimals: 18, // WRONG
},
},
},
},
}
err := cfg.Validate(v.GetTestContext(), v.omniClient)
// we should error because the decimals are wrong
v.Require().Error(err)
}
func (v *ValidateDecimalsSuite) TestValidateCorrectDecimals() {
cfg := relconfig.Config{
Chains: map[int]relconfig.ChainConfig{
1: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
Address: usdcAddr,
Decimals: 6,
},
},
},
},
}
err := cfg.Validate(v.GetTestContext(), v.omniClient)
v.Require().NoError(err)
}
func (v *ValidateDecimalsSuite) TestMixtureDecimals() {
cfg := relconfig.Config{
Chains: map[int]relconfig.ChainConfig{
1: {
Tokens: map[string]relconfig.TokenConfig{
"USDC": {
Address: usdcAddr,
Decimals: 6,
},
},
},
42161: {
Tokens: map[string]relconfig.TokenConfig{
"ARB": {
Address: arbAddr,
Decimals: 18,
},
},
},
10: {
Tokens: map[string]relconfig.TokenConfig{
"OP": {
Address: opAddr,
Decimals: 69,
},
},
},
},
}
err := cfg.Validate(v.GetTestContext(), v.omniClient)
v.Require().Error(err)
}