diff --git a/go.mod b/go.mod index d8de52a..b537b60 100644 --- a/go.mod +++ b/go.mod @@ -119,6 +119,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/status-im/keycard-go v0.2.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect diff --git a/go.sum b/go.sum index 0d2d1e7..cd2e783 100644 --- a/go.sum +++ b/go.sum @@ -294,6 +294,8 @@ github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cA github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/utils/bot/balance_on_chain/monitor.go b/utils/bot/balance_on_chain/monitor.go index 09f1301..d97212d 100644 --- a/utils/bot/balance_on_chain/monitor.go +++ b/utils/bot/balance_on_chain/monitor.go @@ -2,11 +2,12 @@ package balance_on_chain import ( "context" + "omni-balance/utils/bot" + "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/shopspring/decimal" "github.com/sirupsen/logrus" - "omni-balance/utils/bot" ) func init() { diff --git a/utils/bot/balance_on_chain/monitor_test.go b/utils/bot/balance_on_chain/monitor_test.go new file mode 100644 index 0000000..8d97866 --- /dev/null +++ b/utils/bot/balance_on_chain/monitor_test.go @@ -0,0 +1,86 @@ +package balance_on_chain + +import ( + "context" + "omni-balance/utils/bot" + "omni-balance/utils/configs" + "omni-balance/utils/constant" + "omni-balance/utils/wallets/wallet_mocks" + "strconv" + "testing" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" +) + +func TestBalanceOnChain_Check(t *testing.T) { + testConf := &configs.Config{ + Chains: []configs.Chain{ + { + Id: 1, + Name: constant.Ethereum, + NativeToken: "ETH", + Tokens: []configs.Token{ + { + ContractAddress: "0x0000000000000000000000000000000000000000", + Decimals: 18, + Name: "ETH", + }, + }, + }, + }, + Wallets: []configs.Wallet{ + configs.Wallet{ + Address: constant.ZeroAddress.Hex(), + Tokens: []configs.WalletToken{ + configs.WalletToken{ + Name: "ETH", + Amount: decimal.RequireFromString("1"), + Threshold: decimal.RequireFromString("1000"), + Chains: []string{constant.Ethereum}, + }, + }, + }, + }, + } + testConf.Init() + b := new(BalanceOnChain) + w := wallet_mocks.NewWallets(t) + w.On("GetExternalBalance", context.Background(), constant.ZeroAddress, int32(18), nil).Return(decimal.RequireFromString("1000"), nil) + w.On("GetAddress").Return(constant.ZeroAddress) + tasks, Type, err := b.Check(context.Background(), bot.Params{ + Conf: *testConf, + Info: bot.Config{ + Wallet: w, + TokenName: "ETH", + Chain: constant.Ethereum, + }, + }) + assert.NoError(t, err) + assert.Equal(t, bot.Queue, Type) + assert.Len(t, tasks, 1) + assert.Equal(t, "1", tasks[0].Amount.String()) + assert.Equal(t, constant.ZeroAddress.Hex(), tasks[0].Wallet) + assert.Equal(t, "ETH", tasks[0].TokenOutName) + assert.Equal(t, constant.Ethereum, tasks[0].TokenOutChainName) + + w = wallet_mocks.NewWallets(t) + w.On("GetExternalBalance", context.Background(), constant.ZeroAddress, int32(18), nil).Return(decimal.RequireFromString("1"), nil) + w.On("GetAddress").Return(constant.ZeroAddress) + tasks, Type, err = b.Check(context.Background(), bot.Params{ + Conf: *testConf, + Info: bot.Config{ + Wallet: w, + TokenName: "ETH", + Chain: constant.Ethereum, + }, + }) + assert.NoError(t, err) + assert.Equal(t, bot.Queue, Type) + assert.Len(t, tasks, 1) + // 1000 + (1000 * 0.3) + assert.Equal(t, strconv.Itoa(1000+(1000*0.3)), tasks[0].Amount.String()) + assert.Equal(t, constant.ZeroAddress.Hex(), tasks[0].Wallet) + assert.Equal(t, "ETH", tasks[0].TokenOutName) + assert.Equal(t, constant.Ethereum, tasks[0].TokenOutChainName) +} diff --git a/utils/bot/gate_liquidity/gate_liquidity.go b/utils/bot/gate_liquidity/gate_liquidity.go index b851206..77e0cc1 100644 --- a/utils/bot/gate_liquidity/gate_liquidity.go +++ b/utils/bot/gate_liquidity/gate_liquidity.go @@ -14,7 +14,7 @@ func init() { } type GateLiquidity struct { - balance_on_chain.BalanceOnChain + Bot bot.Bot } func (g GateLiquidity) Name() string { @@ -22,7 +22,10 @@ func (g GateLiquidity) Name() string { } func (b GateLiquidity) Check(ctx context.Context, args bot.Params) ([]bot.Task, bot.ProcessType, error) { - tasks, processType, err := b.BalanceOnChain.Check(ctx, args) + if b.Bot == nil { + b.Bot = balance_on_chain.BalanceOnChain{} + } + tasks, processType, err := b.Bot.Check(ctx, args) if err != nil { return nil, processType, err } diff --git a/utils/bot/gate_liquidity/gate_liquidity_test.go b/utils/bot/gate_liquidity/gate_liquidity_test.go new file mode 100644 index 0000000..8f53002 --- /dev/null +++ b/utils/bot/gate_liquidity/gate_liquidity_test.go @@ -0,0 +1,90 @@ +package gate_liquidity + +import ( + "context" + "omni-balance/utils/bot" + "omni-balance/utils/bot_mocks" + "omni-balance/utils/configs" + "omni-balance/utils/constant" + "testing" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestGateLiquidity_Check(t *testing.T) { + testConf := &configs.Config{ + Chains: []configs.Chain{ + { + Id: 1, + Name: constant.Ethereum, + NativeToken: "ETH", + Tokens: []configs.Token{ + { + ContractAddress: "0x0000000000000000000000000000000000000000", + Decimals: 18, + Name: "ETH", + }, + }, + }, + }, + Wallets: []configs.Wallet{ + configs.Wallet{ + Address: constant.ZeroAddress.Hex(), + BotTypes: []configs.BotConfig{ + { + Name: "gate_liquidity", + TokenChains: map[string][]string{ + "ETH": []string{constant.Ethereum}, + }, + Config: map[string]interface{}{ + "toChain": constant.Arbitrum, + }, + }, + }, + Tokens: []configs.WalletToken{ + configs.WalletToken{ + + Name: "ETH", + Amount: decimal.RequireFromString("1"), + Threshold: decimal.RequireFromString("1000"), + Chains: []string{constant.Ethereum}, + }, + }, + }, + }, + } + testConf.Init() + g := new(GateLiquidity) + b := bot_mocks.NewBot(t) + b.On("Check", mock.Anything, mock.Anything).Return( + []bot.Task{ + { + Wallet: constant.ZeroAddress.Hex(), + TokenInName: "ETH", + TokenOutName: "ETH", + TokenOutChainName: constant.Ethereum, + Amount: decimal.RequireFromString("1"), + }, + }, + bot.Queue, + nil, + ) + g.Bot = b + tasks, Type, err := g.Check(context.Background(), bot.Params{ + Conf: *testConf, + Info: bot.Config{ + TokenName: "ETH", + Chain: constant.Ethereum, + }, + }) + assert.NoError(t, err) + assert.Equal(t, bot.Queue, Type) + assert.Len(t, tasks, 1) + assert.Equal(t, "1", tasks[0].Amount.String()) + assert.Equal(t, constant.ZeroAddress.Hex(), tasks[0].Wallet) + assert.Equal(t, "ETH", tasks[0].TokenOutName) + assert.Equal(t, constant.Arbitrum, tasks[0].TokenOutChainName) + assert.Equal(t, constant.Ethereum, tasks[0].TokenInChainName) +} diff --git a/utils/bot/helix_liquidity/aave_debt.go b/utils/bot/helix_liquidity/aave_debt.go index 5752c22..863f705 100644 --- a/utils/bot/helix_liquidity/aave_debt.go +++ b/utils/bot/helix_liquidity/aave_debt.go @@ -56,6 +56,18 @@ var ( }, }, }, + constant.ArbitrumSepolia: AvaeConfig{ + Chain: constant.ArbitrumSepolia, + DebtTokens: map[string]debtTokens{ + "USDC": debtTokens{ + Name: "USDC", + AToken: common.HexToAddress("0x460b97BD498E1157530AEb3086301d5225b91216"), + VToken: common.HexToAddress("0x4fBE3A94C60A5085dA6a2D309965DcF34c36711d"), + UnderlyingToken: common.HexToAddress("0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d"), + Decimals: 6, + }, + }, + }, } ) @@ -80,7 +92,7 @@ type Aave struct { func (a Aave) BalanceOf(ctx context.Context, args DebtParams) (decimal.Decimal, error) { conf, ok := aaveAddressBook[args.Chain] - if !ok { + if !ok || conf.Chain == "" || conf.DebtTokens[args.Token].Name == "" { return decimal.Zero, errors.Errorf("chain %s not support", args.Chain) } atokenBalance, err := chains.GetTokenBalance(ctx, args.Client, conf.DebtTokens[args.Token].AToken.Hex(), @@ -88,6 +100,7 @@ func (a Aave) BalanceOf(ctx context.Context, args DebtParams) (decimal.Decimal, if err != nil { return decimal.Zero, errors.Wrap(err, "get atoken balance error") } + vtokenBalance, err := chains.GetTokenBalance(ctx, args.Client, conf.DebtTokens[args.Token].VToken.Hex(), args.Address.Hex(), conf.DebtTokens[args.Token].Decimals) if err != nil { diff --git a/utils/bot/helix_liquidity/aave_debt_test.go b/utils/bot/helix_liquidity/aave_debt_test.go new file mode 100644 index 0000000..f06c456 --- /dev/null +++ b/utils/bot/helix_liquidity/aave_debt_test.go @@ -0,0 +1,44 @@ +package helix_liquidity + +import ( + "context" + "omni-balance/utils/chains" + "omni-balance/utils/chains/chain_mocks" + "omni-balance/utils/constant" + "omni-balance/utils/erc20" + "strconv" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestAave_BalanceOf(t *testing.T) { + mockClient := chain_mocks.NewMockClient(t) + + erc20Abi, err := erc20.TokenMetaData.GetAbi() + assert.NoError(t, err) + input, err := erc20Abi.Pack("balanceOf", constant.ZeroAddress) + assert.NoError(t, err) + token := common.HexToAddress("0x460b97BD498E1157530AEb3086301d5225b91216") + mockClient.On("CallContract", context.TODO(), ethereum.CallMsg{To: &token, Data: input}, mock.Anything).Return( + chains.EthToWei(decimal.RequireFromString("1000"), 6).Bytes(), nil, + ) + + vtoken := common.HexToAddress("0x4fBE3A94C60A5085dA6a2D309965DcF34c36711d") + mockClient.On("CallContract", context.TODO(), ethereum.CallMsg{To: &vtoken, Data: input}, mock.Anything).Return( + chains.EthToWei(decimal.RequireFromString("900"), 6).Bytes(), nil, + ) + + balance, err := new(Aave).BalanceOf(context.TODO(), DebtParams{ + Address: constant.ZeroAddress, + Token: "USDC", + Client: mockClient, + Chain: constant.ArbitrumSepolia, + }) + assert.NoError(t, err) + assert.Equal(t, balance.String(), strconv.Itoa(1000-900)) +} diff --git a/utils/bot_mocks/bot_mock.go b/utils/bot_mocks/bot_mock.go new file mode 100644 index 0000000..e1be748 --- /dev/null +++ b/utils/bot_mocks/bot_mock.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package bot_mocks + +import ( + context "context" + bot "omni-balance/utils/bot" + + mock "github.com/stretchr/testify/mock" +) + +// Bot is an autogenerated mock type for the Bot type +type Bot struct { + mock.Mock +} + +// Check provides a mock function with given fields: ctx, args +func (_m *Bot) Check(ctx context.Context, args bot.Params) ([]bot.Task, bot.ProcessType, error) { + ret := _m.Called(ctx, args) + + if len(ret) == 0 { + panic("no return value specified for Check") + } + + var r0 []bot.Task + var r1 bot.ProcessType + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, bot.Params) ([]bot.Task, bot.ProcessType, error)); ok { + return rf(ctx, args) + } + if rf, ok := ret.Get(0).(func(context.Context, bot.Params) []bot.Task); ok { + r0 = rf(ctx, args) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]bot.Task) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, bot.Params) bot.ProcessType); ok { + r1 = rf(ctx, args) + } else { + r1 = ret.Get(1).(bot.ProcessType) + } + + if rf, ok := ret.Get(2).(func(context.Context, bot.Params) error); ok { + r2 = rf(ctx, args) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// Name provides a mock function with given fields: +func (_m *Bot) Name() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Name") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// NewBot creates a new instance of Bot. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewBot(t interface { + mock.TestingT + Cleanup(func()) +}) *Bot { + mock := &Bot{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/utils/chains/chain_mocks/MockClient.go b/utils/chains/chain_mocks/MockClient.go new file mode 100644 index 0000000..94ac814 --- /dev/null +++ b/utils/chains/chain_mocks/MockClient.go @@ -0,0 +1,889 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package chain_mocks + +import ( + big "math/big" + + common "github.com/ethereum/go-ethereum/common" + + context "context" + + ethereum "github.com/ethereum/go-ethereum" + + mock "github.com/stretchr/testify/mock" + + types "github.com/ethereum/go-ethereum/core/types" +) + +// MockClient is an autogenerated mock type for the MockClient type +type MockClient struct { + mock.Mock +} + +// BalanceAt provides a mock function with given fields: ctx, account, blockNumber +func (_m *MockClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { + ret := _m.Called(ctx, account, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for BalanceAt") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) (*big.Int, error)); ok { + return rf(ctx, account, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) *big.Int); ok { + r0 = rf(ctx, account, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, *big.Int) error); ok { + r1 = rf(ctx, account, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockByHash provides a mock function with given fields: ctx, hash +func (_m *MockClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + ret := _m.Called(ctx, hash) + + if len(ret) == 0 { + panic("no return value specified for BlockByHash") + } + + var r0 *types.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (*types.Block, error)); ok { + return rf(ctx, hash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) *types.Block); ok { + r0 = rf(ctx, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockByNumber provides a mock function with given fields: ctx, number +func (_m *MockClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + ret := _m.Called(ctx, number) + + if len(ret) == 0 { + panic("no return value specified for BlockByNumber") + } + + var r0 *types.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Block, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Block); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockNumber provides a mock function with given fields: ctx +func (_m *MockClient) BlockNumber(ctx context.Context) (uint64, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for BlockNumber") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (uint64, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) uint64); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CallContract provides a mock function with given fields: ctx, call, blockNumber +func (_m *MockClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, call, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for CallContract") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error)); ok { + return rf(ctx, call, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg, *big.Int) []byte); ok { + r0 = rf(ctx, call, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg, *big.Int) error); ok { + r1 = rf(ctx, call, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ChainID provides a mock function with given fields: ctx +func (_m *MockClient) ChainID(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ChainID") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CodeAt provides a mock function with given fields: ctx, account, blockNumber +func (_m *MockClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, account, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for CodeAt") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) ([]byte, error)); ok { + return rf(ctx, account, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) []byte); ok { + r0 = rf(ctx, account, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, *big.Int) error); ok { + r1 = rf(ctx, account, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// EstimateGas provides a mock function with given fields: ctx, call +func (_m *MockClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + ret := _m.Called(ctx, call) + + if len(ret) == 0 { + panic("no return value specified for EstimateGas") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) (uint64, error)); ok { + return rf(ctx, call) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) uint64); ok { + r0 = rf(ctx, call) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg) error); ok { + r1 = rf(ctx, call) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FeeHistory provides a mock function with given fields: ctx, blockCount, lastBlock, rewardPercentiles +func (_m *MockClient) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, lastBlock, rewardPercentiles) + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 *ethereum.FeeHistory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, *big.Int, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, lastBlock, rewardPercentiles) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, *big.Int, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, lastBlock, rewardPercentiles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ethereum.FeeHistory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, *big.Int, []float64) error); ok { + r1 = rf(ctx, blockCount, lastBlock, rewardPercentiles) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FilterLogs provides a mock function with given fields: ctx, q +func (_m *MockClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + ret := _m.Called(ctx, q) + + if len(ret) == 0 { + panic("no return value specified for FilterLogs") + } + + var r0 []types.Log + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery) ([]types.Log, error)); ok { + return rf(ctx, q) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery) []types.Log); ok { + r0 = rf(ctx, q) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Log) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.FilterQuery) error); ok { + r1 = rf(ctx, q) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HeaderByHash provides a mock function with given fields: ctx, hash +func (_m *MockClient) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + ret := _m.Called(ctx, hash) + + if len(ret) == 0 { + panic("no return value specified for HeaderByHash") + } + + var r0 *types.Header + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (*types.Header, error)); ok { + return rf(ctx, hash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) *types.Header); ok { + r0 = rf(ctx, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HeaderByNumber provides a mock function with given fields: ctx, number +func (_m *MockClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + ret := _m.Called(ctx, number) + + if len(ret) == 0 { + panic("no return value specified for HeaderByNumber") + } + + var r0 *types.Header + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) (*types.Header, error)); ok { + return rf(ctx, number) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) *types.Header); ok { + r0 = rf(ctx, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, number) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NonceAt provides a mock function with given fields: ctx, account, blockNumber +func (_m *MockClient) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { + ret := _m.Called(ctx, account, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for NonceAt") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) (uint64, error)); ok { + return rf(ctx, account, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) uint64); ok { + r0 = rf(ctx, account, blockNumber) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, *big.Int) error); ok { + r1 = rf(ctx, account, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PendingBalanceAt provides a mock function with given fields: ctx, account +func (_m *MockClient) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) { + ret := _m.Called(ctx, account) + + if len(ret) == 0 { + panic("no return value specified for PendingBalanceAt") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) (*big.Int, error)); ok { + return rf(ctx, account) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address) *big.Int); ok { + r0 = rf(ctx, account) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address) error); ok { + r1 = rf(ctx, account) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PendingCallContract provides a mock function with given fields: ctx, call +func (_m *MockClient) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { + ret := _m.Called(ctx, call) + + if len(ret) == 0 { + panic("no return value specified for PendingCallContract") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) ([]byte, error)); ok { + return rf(ctx, call) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) []byte); ok { + r0 = rf(ctx, call) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg) error); ok { + r1 = rf(ctx, call) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PendingCodeAt provides a mock function with given fields: ctx, account +func (_m *MockClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + ret := _m.Called(ctx, account) + + if len(ret) == 0 { + panic("no return value specified for PendingCodeAt") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) ([]byte, error)); ok { + return rf(ctx, account) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address) []byte); ok { + r0 = rf(ctx, account) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address) error); ok { + r1 = rf(ctx, account) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PendingNonceAt provides a mock function with given fields: ctx, account +func (_m *MockClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + ret := _m.Called(ctx, account) + + if len(ret) == 0 { + panic("no return value specified for PendingNonceAt") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) (uint64, error)); ok { + return rf(ctx, account) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address) uint64); ok { + r0 = rf(ctx, account) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address) error); ok { + r1 = rf(ctx, account) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PendingStorageAt provides a mock function with given fields: ctx, account, key +func (_m *MockClient) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) { + ret := _m.Called(ctx, account, key) + + if len(ret) == 0 { + panic("no return value specified for PendingStorageAt") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, common.Hash) ([]byte, error)); ok { + return rf(ctx, account, key) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, common.Hash) []byte); ok { + r0 = rf(ctx, account, key) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, common.Hash) error); ok { + r1 = rf(ctx, account, key) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PendingTransactionCount provides a mock function with given fields: ctx +func (_m *MockClient) PendingTransactionCount(ctx context.Context) (uint, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for PendingTransactionCount") + } + + var r0 uint + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (uint, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) uint); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(uint) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SendTransaction provides a mock function with given fields: ctx, tx +func (_m *MockClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { + ret := _m.Called(ctx, tx) + + if len(ret) == 0 { + panic("no return value specified for SendTransaction") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction) error); ok { + r0 = rf(ctx, tx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// StorageAt provides a mock function with given fields: ctx, account, key, blockNumber +func (_m *MockClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + ret := _m.Called(ctx, account, key, blockNumber) + + if len(ret) == 0 { + panic("no return value specified for StorageAt") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, common.Hash, *big.Int) ([]byte, error)); ok { + return rf(ctx, account, key, blockNumber) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, common.Hash, *big.Int) []byte); ok { + r0 = rf(ctx, account, key, blockNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, common.Hash, *big.Int) error); ok { + r1 = rf(ctx, account, key, blockNumber) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SubscribeFilterLogs provides a mock function with given fields: ctx, q, ch +func (_m *MockClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + ret := _m.Called(ctx, q, ch) + + if len(ret) == 0 { + panic("no return value specified for SubscribeFilterLogs") + } + + var r0 ethereum.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error)); ok { + return rf(ctx, q, ch) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) ethereum.Subscription); ok { + r0 = rf(ctx, q, ch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ethereum.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.FilterQuery, chan<- types.Log) error); ok { + r1 = rf(ctx, q, ch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SubscribeNewHead provides a mock function with given fields: ctx, ch +func (_m *MockClient) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + ret := _m.Called(ctx, ch) + + if len(ret) == 0 { + panic("no return value specified for SubscribeNewHead") + } + + var r0 ethereum.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, chan<- *types.Header) (ethereum.Subscription, error)); ok { + return rf(ctx, ch) + } + if rf, ok := ret.Get(0).(func(context.Context, chan<- *types.Header) ethereum.Subscription); ok { + r0 = rf(ctx, ch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ethereum.Subscription) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, chan<- *types.Header) error); ok { + r1 = rf(ctx, ch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SuggestGasPrice provides a mock function with given fields: ctx +func (_m *MockClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasPrice") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SuggestGasTipCap provides a mock function with given fields: ctx +func (_m *MockClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasTipCap") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TransactionByHash provides a mock function with given fields: ctx, txHash +func (_m *MockClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + ret := _m.Called(ctx, txHash) + + if len(ret) == 0 { + panic("no return value specified for TransactionByHash") + } + + var r0 *types.Transaction + var r1 bool + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (*types.Transaction, bool, error)); ok { + return rf(ctx, txHash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) *types.Transaction); ok { + r0 = rf(ctx, txHash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) bool); ok { + r1 = rf(ctx, txHash) + } else { + r1 = ret.Get(1).(bool) + } + + if rf, ok := ret.Get(2).(func(context.Context, common.Hash) error); ok { + r2 = rf(ctx, txHash) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// TransactionCount provides a mock function with given fields: ctx, blockHash +func (_m *MockClient) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { + ret := _m.Called(ctx, blockHash) + + if len(ret) == 0 { + panic("no return value specified for TransactionCount") + } + + var r0 uint + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (uint, error)); ok { + return rf(ctx, blockHash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) uint); ok { + r0 = rf(ctx, blockHash) + } else { + r0 = ret.Get(0).(uint) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, blockHash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TransactionInBlock provides a mock function with given fields: ctx, blockHash, index +func (_m *MockClient) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { + ret := _m.Called(ctx, blockHash, index) + + if len(ret) == 0 { + panic("no return value specified for TransactionInBlock") + } + + var r0 *types.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash, uint) (*types.Transaction, error)); ok { + return rf(ctx, blockHash, index) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash, uint) *types.Transaction); ok { + r0 = rf(ctx, blockHash, index) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash, uint) error); ok { + r1 = rf(ctx, blockHash, index) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TransactionReceipt provides a mock function with given fields: ctx, txHash +func (_m *MockClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + ret := _m.Called(ctx, txHash) + + if len(ret) == 0 { + panic("no return value specified for TransactionReceipt") + } + + var r0 *types.Receipt + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (*types.Receipt, error)); ok { + return rf(ctx, txHash) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash) *types.Receipt); ok { + r0 = rf(ctx, txHash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Receipt) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { + r1 = rf(ctx, txHash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/utils/chains/client.go b/utils/chains/client.go index 8b7359e..560773e 100644 --- a/utils/chains/client.go +++ b/utils/chains/client.go @@ -13,6 +13,10 @@ import ( "sync/atomic" ) +type MockClient interface { + simulated.Client +} + type Client struct { index atomic.Int64 clients []*ethclient.Client diff --git a/utils/chains/eip712_test.go b/utils/chains/eip712_test.go new file mode 100644 index 0000000..59991cc --- /dev/null +++ b/utils/chains/eip712_test.go @@ -0,0 +1,118 @@ +package chains + +import ( + "math/big" + "omni-balance/utils/constant" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/stretchr/testify/assert" +) + +func TestEncodeForSigning(t *testing.T) { + // Define a mock TypedData for testing + typedData := apitypes.TypedData{ + Types: apitypes.Types{ + "EIP712Domain": []apitypes.Type{ + {Name: "name", Type: "string"}, + {Name: "chainId", Type: "uint256"}, + {Name: "verifyingContract", Type: "address"}, + }, + "PermitDetails": []apitypes.Type{ + {Name: "token", Type: "address"}, + {Name: "amount", Type: "uint160"}, + {Name: "expiration", Type: "uint48"}, + {Name: "nonce", Type: "uint48"}, + }, + "PermitSingle": []apitypes.Type{ + {Name: "details", Type: "PermitDetails"}, + {Name: "spender", Type: "address"}, + {Name: "sigDeadline", Type: "uint256"}, + }, + }, + Domain: apitypes.TypedDataDomain{ + Name: "PermitSingle", + ChainId: math.NewHexOrDecimal256(1), + VerifyingContract: constant.ZeroAddress.Hex(), + }, + PrimaryType: "PermitSingle", + Message: map[string]interface{}{ + "details": map[string]interface{}{ + "token": constant.ZeroAddress.Hex(), + "amount": big.NewInt(0), + "expiration": big.NewInt(1721024951), + "nonce": big.NewInt(1), + }, + "spender": constant.ZeroAddress.Hex(), + "sigDeadline": big.NewInt(1721024951), + }, + } + + // Define the expected hash result + expectedHash := "0x0197a3976d5059736a317a039e4328fbbbe328c1a330e644ea29fc7beddb7fdb" + + // Call the EncodeForSigning function + hash, err := EncodeForSigning(typedData) + + // Assert that there is no error + assert.NoError(t, err) + + // Assert that the returned hash matches the expected hash + assert.Equal(t, expectedHash, hash.String()) +} + +func TestSignTypedData(t *testing.T) { + // Define a mock TypedData for testing + typedData := apitypes.TypedData{ + Types: apitypes.Types{ + "EIP712Domain": []apitypes.Type{ + {Name: "name", Type: "string"}, + {Name: "chainId", Type: "uint256"}, + {Name: "verifyingContract", Type: "address"}, + }, + "PermitDetails": []apitypes.Type{ + {Name: "token", Type: "address"}, + {Name: "amount", Type: "uint160"}, + {Name: "expiration", Type: "uint48"}, + {Name: "nonce", Type: "uint48"}, + }, + "PermitSingle": []apitypes.Type{ + {Name: "details", Type: "PermitDetails"}, + {Name: "spender", Type: "address"}, + {Name: "sigDeadline", Type: "uint256"}, + }, + }, + Domain: apitypes.TypedDataDomain{ + Name: "PermitSingle", + ChainId: math.NewHexOrDecimal256(1), + VerifyingContract: constant.ZeroAddress.Hex(), + }, + PrimaryType: "PermitSingle", + Message: map[string]interface{}{ + "details": map[string]interface{}{ + "token": constant.ZeroAddress.Hex(), + "amount": big.NewInt(0), + "expiration": big.NewInt(1721024951), + "nonce": big.NewInt(1), + }, + "spender": constant.ZeroAddress.Hex(), + "sigDeadline": big.NewInt(1721024951), + }, + } + + // Define the expected hash result + expectedHash := "df31dfa39a6b758368cf9711484926ff65d40eeecb77d51c3c185b73a4239b4a59dc6f4a993b97938b87ffc2269dfc2a7931e2f59e8fe8bfd39138e84e8d13981b" + + // Call the EncodeForSigning function + hash, err := SignTypedData(typedData, func(msg []byte) (sig []byte, err error) { + return SignMsg(msg, constant.TestPrivateKey) + }) + + // Assert that there is no error + assert.NoError(t, err) + + // Assert that the returned hash matches the expected hash + assert.Equal(t, expectedHash, common.Bytes2Hex(hash)) +} diff --git a/utils/chains/util.go b/utils/chains/util.go index 76bbced..088cbb2 100644 --- a/utils/chains/util.go +++ b/utils/chains/util.go @@ -2,6 +2,15 @@ package chains import ( "context" + + "math/big" + "omni-balance/utils" + "omni-balance/utils/constant" + "omni-balance/utils/erc20" + "omni-balance/utils/error_types" + "strings" + "time" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -12,13 +21,6 @@ import ( "github.com/shopspring/decimal" "github.com/sirupsen/logrus" "github.com/spf13/cast" - "math/big" - "omni-balance/utils" - "omni-balance/utils/constant" - "omni-balance/utils/erc20" - "omni-balance/utils/error_types" - "strings" - "time" ) func getDecimals(decimals ...int32) int32 { @@ -63,6 +65,7 @@ func GetTokenBalance(ctx context.Context, client simulated.Client, tokenAddress, } return WeiToEth(balance, tokenDecimals), nil } + erc20Abi, err := erc20.TokenMetaData.GetAbi() if err != nil { return decimal.Zero, errors.Wrap(err, "get erc20 abi error") diff --git a/utils/chains/util_test.go b/utils/chains/util_test.go new file mode 100644 index 0000000..1120469 --- /dev/null +++ b/utils/chains/util_test.go @@ -0,0 +1,79 @@ +package chains + +import ( + "context" + "math/big" + "omni-balance/utils/chains/chain_mocks" + "omni-balance/utils/constant" + "omni-balance/utils/erc20" + "omni-balance/utils/error_types" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// TestBuildSendToken tests the BuildSendToken function +func TestBuildSendToken(t *testing.T) { + + var ( + mockClient = new(chain_mocks.MockClient) + ctx = context.Background() + ) + + mockClient.On("BalanceAt", ctx, constant.ZeroAddress, mock.Anything).Return(EthToWei(decimal.RequireFromString("1000"), 18), nil) + tx, err := BuildSendToken(ctx, SendTokenParams{ + Client: mockClient, + Sender: constant.ZeroAddress, + TokenAddress: constant.ZeroAddress, + TokenDecimals: 18, + ToAddress: constant.ZeroAddress, + AmountWei: decimal.NewFromBigInt(EthToWei(decimal.RequireFromString("1000"), 18), 0), + }) + assert.NoError(t, err) + assert.NotNil(t, tx) + assert.Equal(t, constant.ZeroAddress.Hex(), tx.To.Hex()) + assert.Equal(t, EthToWei(decimal.RequireFromString("1000"), 18).String(), tx.Value.String()) + assert.Equal(t, "", common.Bytes2Hex(tx.Data)) + + mockClient = new(chain_mocks.MockClient) + mockClient.On("BalanceAt", ctx, constant.ZeroAddress, mock.Anything).Return(EthToWei(decimal.RequireFromString("0"), 18), nil) + tx, err = BuildSendToken(ctx, SendTokenParams{ + Client: mockClient, + Sender: constant.ZeroAddress, + TokenAddress: constant.ZeroAddress, + TokenDecimals: 18, + ToAddress: constant.ZeroAddress, + AmountWei: decimal.NewFromBigInt(EthToWei(decimal.RequireFromString("1000"), 18), 0), + }) + assert.Error(t, err) + assert.Equal(t, err.Error(), error_types.ErrInsufficientBalance.Error()) + assert.Nil(t, tx) + + // erc20 token + mockClient = new(chain_mocks.MockClient) + mockClient.On("BalanceAt", ctx, constant.ZeroAddress, mock.Anything).Return(EthToWei(decimal.RequireFromString("1000"), 6), nil) + erc20Abi, err := erc20.TokenMetaData.GetAbi() + assert.NoError(t, err) + input, err := erc20Abi.Pack("balanceOf", constant.ZeroAddress) + assert.NoError(t, err) + token := common.HexToAddress("0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d") + mockClient.On("CallContract", ctx, ethereum.CallMsg{To: &token, Data: input}, mock.Anything).Return(big.NewInt(1000000000000).Bytes(), nil) + + tx, err = BuildSendToken(ctx, SendTokenParams{ + Client: mockClient, + Sender: constant.ZeroAddress, + TokenAddress: token, + TokenDecimals: 18, + ToAddress: constant.ZeroAddress, + AmountWei: decimal.NewFromBigInt(EthToWei(decimal.RequireFromString("1000"), 6), 0), + }) + assert.NoError(t, err) + assert.NotNil(t, tx) + assert.Equal(t, token.Hex(), tx.To.Hex()) + assert.Nil(t, tx.Value) + assert.Equal(t, "a9059cbb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003b9aca00", common.Bytes2Hex(tx.Data)) +} diff --git a/utils/constant/chain.go b/utils/constant/chain.go index be89cd4..67b6e1b 100644 --- a/utils/constant/chain.go +++ b/utils/constant/chain.go @@ -3,36 +3,37 @@ package constant import "golang.org/x/exp/constraints" const ( - Zksync = "zksync" - Sepolia = "sepolia" - Polygon = "polygon" - PolygonZkEvm = "polygon-zkEvm" - Bsc = "bsc" - Blast = "blast" - DarwiniaDvm = "darwinia-dvm" - Base = "base" - Arbitrum = "arbitrum" - Gnosis = "gnosis" - Scroll = "scroll" - Optimism = "op" - AstarZkevm = "astar-zkevm" - Mantle = "mantle" - Linea = "linea" - CrabDvm = "crab-dvm" - Ethereum = "ethereum" - Merlin = "merlin" - Avalanche = "avalanche" - Mode = "mode" - Cronos = "cronos" - PulseChain = "pulseChain" - Kava = "kava" - ZkLinkNova = "zkLink-nova" - Rootstock = "rootstock" - Astar = "astar" - OpBNB = "opBNB" - Bnb = "bnb" - Celo = "celo" - Moonbeam = "moonbeam" + Zksync = "zksync" + Sepolia = "sepolia" + Polygon = "polygon" + PolygonZkEvm = "polygon-zkEvm" + Bsc = "bsc" + Blast = "blast" + DarwiniaDvm = "darwinia-dvm" + Base = "base" + Arbitrum = "arbitrum" + Gnosis = "gnosis" + Scroll = "scroll" + Optimism = "op" + AstarZkevm = "astar-zkevm" + Mantle = "mantle" + Linea = "linea" + CrabDvm = "crab-dvm" + Ethereum = "ethereum" + Merlin = "merlin" + Avalanche = "avalanche" + Mode = "mode" + Cronos = "cronos" + PulseChain = "pulseChain" + Kava = "kava" + ZkLinkNova = "zkLink-nova" + Rootstock = "rootstock" + Astar = "astar" + OpBNB = "opBNB" + Bnb = "bnb" + Celo = "celo" + Moonbeam = "moonbeam" + ArbitrumSepolia = "arbitrum-sepolia" ) const ( @@ -44,36 +45,37 @@ const ( var ( chainName2Id = map[string]int{ - Moonbeam: 1284, - Sepolia: 11155111, - Zksync: 324, - Polygon: 137, - PolygonZkEvm: 1101, - Bsc: 56, - Blast: 81457, - DarwiniaDvm: 46, - Base: 8453, - Arbitrum: 42161, - Gnosis: 100, - Scroll: 534352, - Optimism: 10, - AstarZkevm: 3776, - Mantle: 5000, - Linea: 59144, - CrabDvm: 44, - Ethereum: 1, - Merlin: 4200, - Avalanche: 43114, - Mode: 34443, - Cronos: 25, - PulseChain: 369, - Kava: 2222, - ZkLinkNova: 810180, - Rootstock: 30, - Astar: 592, - OpBNB: 204, - Bnb: 56, - Celo: 42220, + ArbitrumSepolia: 421614, + Moonbeam: 1284, + Sepolia: 11155111, + Zksync: 324, + Polygon: 137, + PolygonZkEvm: 1101, + Bsc: 56, + Blast: 81457, + DarwiniaDvm: 46, + Base: 8453, + Arbitrum: 42161, + Gnosis: 100, + Scroll: 534352, + Optimism: 10, + AstarZkevm: 3776, + Mantle: 5000, + Linea: 59144, + CrabDvm: 44, + Ethereum: 1, + Merlin: 4200, + Avalanche: 43114, + Mode: 34443, + Cronos: 25, + PulseChain: 369, + Kava: 2222, + ZkLinkNova: 810180, + Rootstock: 30, + Astar: 592, + OpBNB: 204, + Bnb: 56, + Celo: 42220, } chainId2Name = make(map[int]string) ) diff --git a/utils/wallets/wallet_mocks/Wallets.go b/utils/wallets/wallet_mocks/Wallets.go new file mode 100644 index 0000000..5b5fe08 --- /dev/null +++ b/utils/wallets/wallet_mocks/Wallets.go @@ -0,0 +1,338 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package wallet_mocks + +import ( + context "context" + + common "github.com/ethereum/go-ethereum/common" + + decimal "github.com/shopspring/decimal" + + mock "github.com/stretchr/testify/mock" + + simulated "github.com/ethereum/go-ethereum/ethclient/simulated" + + types "github.com/ethereum/go-ethereum/core/types" +) + +// Wallets is an autogenerated mock type for the Wallets type +type Wallets struct { + mock.Mock +} + +// CheckFullAccess provides a mock function with given fields: ctx +func (_m *Wallets) CheckFullAccess(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for CheckFullAccess") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetAddress provides a mock function with given fields: isReal +func (_m *Wallets) GetAddress(isReal ...bool) common.Address { + _va := make([]interface{}, len(isReal)) + for _i := range isReal { + _va[_i] = isReal[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for GetAddress") + } + + var r0 common.Address + if rf, ok := ret.Get(0).(func(...bool) common.Address); ok { + r0 = rf(isReal...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Address) + } + } + + return r0 +} + +// GetBalance provides a mock function with given fields: ctx, tokenAddress, decimals, client +func (_m *Wallets) GetBalance(ctx context.Context, tokenAddress common.Address, decimals int32, client simulated.Client) (decimal.Decimal, error) { + ret := _m.Called(ctx, tokenAddress, decimals, client) + + if len(ret) == 0 { + panic("no return value specified for GetBalance") + } + + var r0 decimal.Decimal + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, int32, simulated.Client) (decimal.Decimal, error)); ok { + return rf(ctx, tokenAddress, decimals, client) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, int32, simulated.Client) decimal.Decimal); ok { + r0 = rf(ctx, tokenAddress, decimals, client) + } else { + r0 = ret.Get(0).(decimal.Decimal) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, int32, simulated.Client) error); ok { + r1 = rf(ctx, tokenAddress, decimals, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExternalBalance provides a mock function with given fields: ctx, tokenAddress, decimals, client +func (_m *Wallets) GetExternalBalance(ctx context.Context, tokenAddress common.Address, decimals int32, client simulated.Client) (decimal.Decimal, error) { + ret := _m.Called(ctx, tokenAddress, decimals, client) + + if len(ret) == 0 { + panic("no return value specified for GetExternalBalance") + } + + var r0 decimal.Decimal + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, int32, simulated.Client) (decimal.Decimal, error)); ok { + return rf(ctx, tokenAddress, decimals, client) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, int32, simulated.Client) decimal.Decimal); ok { + r0 = rf(ctx, tokenAddress, decimals, client) + } else { + r0 = ret.Get(0).(decimal.Decimal) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, int32, simulated.Client) error); ok { + r1 = rf(ctx, tokenAddress, decimals, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetNonce provides a mock function with given fields: ctx, client +func (_m *Wallets) GetNonce(ctx context.Context, client simulated.Client) (uint64, error) { + ret := _m.Called(ctx, client) + + if len(ret) == 0 { + panic("no return value specified for GetNonce") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, simulated.Client) (uint64, error)); ok { + return rf(ctx, client) + } + if rf, ok := ret.Get(0).(func(context.Context, simulated.Client) uint64); ok { + r0 = rf(ctx, client) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, simulated.Client) error); ok { + r1 = rf(ctx, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetRealHash provides a mock function with given fields: ctx, txHash, client +func (_m *Wallets) GetRealHash(ctx context.Context, txHash common.Hash, client simulated.Client) (common.Hash, error) { + ret := _m.Called(ctx, txHash, client) + + if len(ret) == 0 { + panic("no return value specified for GetRealHash") + } + + var r0 common.Hash + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash, simulated.Client) (common.Hash, error)); ok { + return rf(ctx, txHash, client) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Hash, simulated.Client) common.Hash); ok { + r0 = rf(ctx, txHash, client) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Hash, simulated.Client) error); ok { + r1 = rf(ctx, txHash, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsDifferentAddress provides a mock function with given fields: +func (_m *Wallets) IsDifferentAddress() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for IsDifferentAddress") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// IsSupportEip712 provides a mock function with given fields: +func (_m *Wallets) IsSupportEip712() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for IsSupportEip712") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// MarshalJSON provides a mock function with given fields: +func (_m *Wallets) MarshalJSON() ([]byte, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for MarshalJSON") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func() ([]byte, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []byte); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SendTransaction provides a mock function with given fields: ctx, tx, client +func (_m *Wallets) SendTransaction(ctx context.Context, tx *types.LegacyTx, client simulated.Client) (common.Hash, error) { + ret := _m.Called(ctx, tx, client) + + if len(ret) == 0 { + panic("no return value specified for SendTransaction") + } + + var r0 common.Hash + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *types.LegacyTx, simulated.Client) (common.Hash, error)); ok { + return rf(ctx, tx, client) + } + if rf, ok := ret.Get(0).(func(context.Context, *types.LegacyTx, simulated.Client) common.Hash); ok { + r0 = rf(ctx, tx, client) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *types.LegacyTx, simulated.Client) error); ok { + r1 = rf(ctx, tx, client) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SignRawMessage provides a mock function with given fields: msg +func (_m *Wallets) SignRawMessage(msg []byte) ([]byte, error) { + ret := _m.Called(msg) + + if len(ret) == 0 { + panic("no return value specified for SignRawMessage") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func([]byte) ([]byte, error)); ok { + return rf(msg) + } + if rf, ok := ret.Get(0).(func([]byte) []byte); ok { + r0 = rf(msg) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(msg) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// WaitTransaction provides a mock function with given fields: ctx, txHash, client +func (_m *Wallets) WaitTransaction(ctx context.Context, txHash common.Hash, client simulated.Client) error { + ret := _m.Called(ctx, txHash, client) + + if len(ret) == 0 { + panic("no return value specified for WaitTransaction") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, common.Hash, simulated.Client) error); ok { + r0 = rf(ctx, txHash, client) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewWallets creates a new instance of Wallets. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewWallets(t interface { + mock.TestingT + Cleanup(func()) +}) *Wallets { + mock := &Wallets{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +}