-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from gateway-fm/API-100
API-100: Position liquidated event
- Loading branch information
Showing
14 changed files
with
703 additions
and
4 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package events | ||
|
||
import ( | ||
"context" | ||
"github.com/ethereum/go-ethereum/ethclient" | ||
"github.com/ethereum/go-ethereum/event" | ||
"github.com/gateway-fm/perpsv3-Go/contracts/perpsMarketGoerli" | ||
"github.com/gateway-fm/perpsv3-Go/errors" | ||
"github.com/gateway-fm/perpsv3-Go/models" | ||
"github.com/gateway-fm/perpsv3-Go/pkg/logger" | ||
"math/big" | ||
) | ||
|
||
// LiquidationSubscription is a struct for listening to all 'PositionLiquidated' contract events and return them as models.Liquidation struct | ||
type LiquidationSubscription struct { | ||
*basicSubscription | ||
LiquidationsChan chan *models.Liquidation | ||
contractEventChan chan *perpsMarketGoerli.PerpsMarketGoerliPositionLiquidated | ||
} | ||
|
||
func (e *Events) ListenLiquidations() (*LiquidationSubscription, error) { | ||
contractEventChan := make(chan *perpsMarketGoerli.PerpsMarketGoerliPositionLiquidated) | ||
|
||
contractSub, err := e.perpsMarket.WatchPositionLiquidated(nil, contractEventChan, nil, nil) | ||
if err != nil { | ||
logger.Log().WithField("layer", "Events-PositionLiquidated").Errorf("error watch order committed: %v", err.Error()) | ||
return nil, errors.GetEventListenErr(err, "PositionLiquidated") | ||
} | ||
|
||
orderSub := newLiquidationSubscription(contractSub, contractEventChan) | ||
|
||
go orderSub.listen(e.rpcClient) | ||
|
||
return orderSub, nil | ||
} | ||
|
||
// newLiquidationSubscription is used to create new LiquidationSubscription instance | ||
func newLiquidationSubscription(eventSub event.Subscription, contractEventChan chan *perpsMarketGoerli.PerpsMarketGoerliPositionLiquidated) *LiquidationSubscription { | ||
return &LiquidationSubscription{ | ||
basicSubscription: newBasicSubscription(eventSub), | ||
contractEventChan: contractEventChan, | ||
LiquidationsChan: make(chan *models.Liquidation), | ||
} | ||
} | ||
|
||
// listen is used to run a goroutine | ||
func (s *LiquidationSubscription) listen(rpcClient *ethclient.Client) { | ||
for { | ||
select { | ||
case <-s.stop: | ||
close(s.LiquidationsChan) | ||
close(s.contractEventChan) | ||
return | ||
case err := <-s.eventSub.Err(): | ||
logger.Log().WithField("layer", "Events-PositionLiquidated").Errorf("error listening position liquidated: %v", err.Error()) | ||
s.ErrChan <- err | ||
continue | ||
case positionLiquidated := <-s.contractEventChan: | ||
block, err := rpcClient.HeaderByNumber(context.Background(), big.NewInt(int64(positionLiquidated.Raw.BlockNumber))) | ||
time := uint64(0) | ||
if err != nil { | ||
logger.Log().WithField("layer", "Events-PositionLiquidated").Warningf( | ||
"error fetching block number %v: %v; order event time set to 0 ", | ||
positionLiquidated.Raw.BlockNumber, err.Error(), | ||
) | ||
s.ErrChan <- err | ||
} else { | ||
time = block.Time | ||
} | ||
|
||
order := models.GetLiquidationFromEvent(positionLiquidated, time) | ||
|
||
s.LiquidationsChan <- order | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package events | ||
|
||
import ( | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/ethclient" | ||
"github.com/gateway-fm/perpsv3-Go/contracts/coreGoerli" | ||
"github.com/gateway-fm/perpsv3-Go/contracts/perpsMarketGoerli" | ||
"github.com/gateway-fm/perpsv3-Go/contracts/spotMarketGoerli" | ||
perps_test "github.com/gateway-fm/perpsv3-Go/utils/testing-contracts/perps-test" | ||
"github.com/stretchr/testify/require" | ||
"log" | ||
"os" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestEvents_ListenLiquidations_OnChain(t *testing.T) { | ||
if os.Getenv("CI") != "" { | ||
t.Skip("Skipping testing in CI environment") | ||
} | ||
|
||
if testing.Short() { | ||
t.Skip("skipping test in short mode.") | ||
} | ||
|
||
rpc := os.Getenv("TEST_RPC_EVENTS") | ||
if rpc == "" { | ||
log.Fatal("no rpc in env vars") | ||
} | ||
|
||
rpcClient, _ := ethclient.Dial(rpc) | ||
|
||
coreC, _ := coreGoerli.NewCoreGoerli(common.HexToAddress("0x76490713314fCEC173f44e99346F54c6e92a8E42"), rpcClient) | ||
spot, _ := spotMarketGoerli.NewSpotMarketGoerli(common.HexToAddress("0x5FF4b3aacdeC86782d8c757FAa638d8790799E83"), rpcClient) | ||
perps, _ := perpsMarketGoerli.NewPerpsMarketGoerli(common.HexToAddress("0xf272382cB3BE898A8CdB1A23BE056fA2Fcf4513b"), rpcClient) | ||
|
||
e := NewEvents(rpcClient, coreC, spot, perps) | ||
|
||
subs, err := e.ListenLiquidations() | ||
require.NoError(t, err) | ||
|
||
stopChan := make(chan struct{}) | ||
|
||
perpsTest := perps_test.GetTestPerpsMarket( | ||
rpc, | ||
"0xf272382cB3BE898A8CdB1A23BE056fA2Fcf4513b", | ||
"0xe487Ad4291019b33e2230F8E2FB1fb6490325260", | ||
420, | ||
) | ||
defer perpsTest.Close() | ||
|
||
go func() { | ||
for { | ||
select { | ||
case <-stopChan: | ||
subs.Close() | ||
return | ||
case err = <-subs.ErrChan: | ||
require.NoError(t, err) | ||
case positionLiquidated := <-subs.LiquidationsChan: | ||
log.Printf("liquidation received, block: %v", positionLiquidated.BlockNumber) | ||
require.NotNil(t, positionLiquidated) | ||
} | ||
} | ||
}() | ||
|
||
//perpsTest.CommitOrder("100", "10000000000000000") | ||
time.Sleep(10 * time.Second) | ||
|
||
close(stopChan) | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package models | ||
|
||
import ( | ||
"math/big" | ||
|
||
"github.com/gateway-fm/perpsv3-Go/contracts/perpsMarketGoerli" | ||
"github.com/gateway-fm/perpsv3-Go/pkg/logger" | ||
) | ||
|
||
// Liquidation is a position liquidation model | ||
// - MarketID: ID of the market used for the order. | ||
// - AccountID: ID of the account used for the order. | ||
// - AmountLiquidated: amount liquidated. | ||
// - CurrentPositionSize: position size after liquidation. | ||
// - BlockNumber: Block number where the order was committed. | ||
// - BlockTimestamp: Timestamp of the block where the order was committed. | ||
type Liquidation struct { | ||
MarketID uint64 | ||
AccountID uint64 | ||
AmountLiquidated *big.Int | ||
CurrentPositionSize *big.Int | ||
BlockNumber uint64 | ||
BlockTimestamp uint64 | ||
} | ||
|
||
// GetLiquidationFromEvent is used to get Liquidation struct from given contract event | ||
func GetLiquidationFromEvent(event *perpsMarketGoerli.PerpsMarketGoerliPositionLiquidated, time uint64) *Liquidation { | ||
if event == nil { | ||
logger.Log().WithField("layer", "Models-Liquidation").Warning("nil event received") | ||
return &Liquidation{} | ||
} | ||
|
||
marketID := uint64(0) | ||
if event.MarketId != nil { | ||
marketID = event.MarketId.Uint64() | ||
} | ||
|
||
accountID := uint64(0) | ||
if event.AccountId != nil { | ||
accountID = event.AccountId.Uint64() | ||
} | ||
|
||
return &Liquidation{ | ||
MarketID: marketID, | ||
AccountID: accountID, | ||
AmountLiquidated: event.AmountLiquidated, | ||
CurrentPositionSize: event.CurrentPositionSize, | ||
BlockNumber: event.Raw.BlockNumber, | ||
BlockTimestamp: time, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package models | ||
|
||
import ( | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/gateway-fm/perpsv3-Go/contracts/perpsMarketGoerli" | ||
"github.com/stretchr/testify/require" | ||
"math/big" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestGetLiquidationFromEvent(t *testing.T) { | ||
timeNow := time.Now() | ||
|
||
testCases := []struct { | ||
name string | ||
event *perpsMarketGoerli.PerpsMarketGoerliPositionLiquidated | ||
time uint64 | ||
want *Liquidation | ||
}{ | ||
{ | ||
name: "nil event", | ||
want: &Liquidation{}, | ||
}, | ||
{ | ||
name: "only market ID", | ||
event: &perpsMarketGoerli.PerpsMarketGoerliPositionLiquidated{ | ||
MarketId: big.NewInt(1), | ||
}, | ||
want: &Liquidation{ | ||
MarketID: uint64(1), | ||
}, | ||
}, | ||
{ | ||
name: "only account ID", | ||
event: &perpsMarketGoerli.PerpsMarketGoerliPositionLiquidated{ | ||
AccountId: big.NewInt(1), | ||
}, | ||
want: &Liquidation{ | ||
AccountID: uint64(1), | ||
}, | ||
}, | ||
{ | ||
name: "full event", | ||
event: &perpsMarketGoerli.PerpsMarketGoerliPositionLiquidated{ | ||
MarketId: big.NewInt(1), | ||
AccountId: big.NewInt(2), | ||
AmountLiquidated: big.NewInt(3), | ||
CurrentPositionSize: big.NewInt(4), | ||
Raw: types.Log{ | ||
BlockNumber: 5, | ||
}, | ||
}, | ||
time: uint64(timeNow.Unix()), | ||
want: &Liquidation{ | ||
MarketID: uint64(1), | ||
AccountID: uint64(2), | ||
AmountLiquidated: big.NewInt(3), | ||
CurrentPositionSize: big.NewInt(4), | ||
BlockNumber: 5, | ||
BlockTimestamp: uint64(timeNow.Unix()), | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range testCases { | ||
t.Run(tt.name, func(t *testing.T) { | ||
res := GetLiquidationFromEvent(tt.event, tt.time) | ||
|
||
require.Equal(t, tt.want, res) | ||
}) | ||
} | ||
} |
Oops, something went wrong.