mirror of
https://github.com/luxfi/graph.git
synced 2026-09-24 00:26:07 +00:00
feat: Liquidity Protocol + Liquid Protocol graph resolvers
Liquidity Protocol resolvers: - CrossChainSwap, LimitOrder, Arbitrage - OMAPool, OMASwap, PriceUpdate - StrategyOrder, MarketMaker, BridgeTransfer Liquid Protocol resolvers: - LiquidFee, ValidatorReward, SlashingEvent - DepositMint, YieldMint, WithdrawBurn - BackingUpdate, VaultStrategy, YieldHarvest Both registered in engine under 'liquidity', 'liquid-protocol', and 'all'.
This commit is contained in:
@@ -25,6 +25,8 @@ import (
|
||||
"github.com/luxfi/graph/resolvers/identity"
|
||||
"github.com/luxfi/graph/resolvers/key"
|
||||
"github.com/luxfi/graph/resolvers/liquid"
|
||||
"github.com/luxfi/graph/resolvers/liquidprotocol"
|
||||
"github.com/luxfi/graph/resolvers/liquidity"
|
||||
"github.com/luxfi/graph/resolvers/oracle"
|
||||
"github.com/luxfi/graph/resolvers/platform"
|
||||
"github.com/luxfi/graph/resolvers/precompile"
|
||||
@@ -177,6 +179,12 @@ func (e *Engine) LoadBuiltin(name string) error {
|
||||
case "liquid", "liquid-staking":
|
||||
e.registerChain(liquid.Register)
|
||||
return nil
|
||||
case "liquidity", "liquidity-protocol", "omnichain":
|
||||
e.registerChain(liquidity.Register)
|
||||
return nil
|
||||
case "liquid-protocol", "teleport", "liquid-vault":
|
||||
e.registerChain(liquidprotocol.Register)
|
||||
return nil
|
||||
case "did", "did-registry":
|
||||
e.registerChain(did.Register)
|
||||
return nil
|
||||
@@ -216,6 +224,8 @@ func (e *Engine) LoadBuiltin(name string) error {
|
||||
e.registerChain(prediction.Register)
|
||||
e.registerChain(securities.Register)
|
||||
e.registerChain(derivatives.Register)
|
||||
e.registerChain(liquidity.Register)
|
||||
e.registerChain(liquidprotocol.Register)
|
||||
return nil
|
||||
case "erc20":
|
||||
e.registerERC20Resolvers()
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
package defi
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDAOTopicsLength(t *testing.T) {
|
||||
topics := DAOTopics()
|
||||
if len(topics) != 18 {
|
||||
t.Fatalf("expected 18 DAO topics, got %d", len(topics))
|
||||
}
|
||||
for _, topic := range topics {
|
||||
if len(topic) != 66 || topic[:2] != "0x" {
|
||||
t.Errorf("invalid topic format: %s", topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGaugeAdded(t *testing.T) {
|
||||
gaugeID := "0x0000000000000000000000000000000000000000000000000000000000000001"
|
||||
data := "0x" +
|
||||
"000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + // recipient
|
||||
"0000000000000000000000000000000000000000000000000000000000000040" + // string offset
|
||||
"0000000000000000000000000000000000000000000000000000000000000004" + // string length
|
||||
"4275726e00000000000000000000000000000000000000000000000000000000" // "Burn"
|
||||
|
||||
logs := []Log{{
|
||||
Address: "0x5555555555555555555555555555555555555555",
|
||||
Topics: []string{TopicGaugeAdded, gaugeID},
|
||||
Data: data,
|
||||
BlockNumber: 100,
|
||||
TxHash: "0xaaa",
|
||||
}}
|
||||
|
||||
gauges, _, _, _ := ParseDAOEvents(logs)
|
||||
if len(gauges) != 1 {
|
||||
t.Fatalf("expected 1 gauge, got %d", len(gauges))
|
||||
}
|
||||
g := gauges[0]
|
||||
if g.GaugeID.Cmp(big.NewInt(1)) != 0 {
|
||||
t.Errorf("gaugeId = %s, want 1", g.GaugeID)
|
||||
}
|
||||
if g.Event != "added" {
|
||||
t.Errorf("event = %s, want added", g.Event)
|
||||
}
|
||||
if g.Recipient != "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
|
||||
t.Errorf("recipient = %s", g.Recipient)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGaugeVote(t *testing.T) {
|
||||
user := "0x000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
gaugeID := "0x0000000000000000000000000000000000000000000000000000000000000002"
|
||||
data := "0x00000000000000000000000000000000000000000000000000000000000007d0" // weight = 2000
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicGaugeVoteCast, user, gaugeID},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
gauges, _, _, _ := ParseDAOEvents(logs)
|
||||
if len(gauges) != 1 {
|
||||
t.Fatalf("expected 1 gauge vote, got %d", len(gauges))
|
||||
}
|
||||
g := gauges[0]
|
||||
if g.User != "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" {
|
||||
t.Errorf("user = %s", g.User)
|
||||
}
|
||||
if g.GaugeID.Cmp(big.NewInt(2)) != 0 {
|
||||
t.Errorf("gaugeId = %s, want 2", g.GaugeID)
|
||||
}
|
||||
if g.Weight.Cmp(big.NewInt(2000)) != 0 {
|
||||
t.Errorf("weight = %s, want 2000", g.Weight)
|
||||
}
|
||||
if g.Event != "vote" {
|
||||
t.Errorf("event = %s, want vote", g.Event)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVLUXDeposit(t *testing.T) {
|
||||
user := "0x000000000000000000000000cccccccccccccccccccccccccccccccccccccccc"
|
||||
lockEnd := "0x0000000000000000000000000000000000000000000000000000000065f5e100"
|
||||
data := "0x" +
|
||||
"00000000000000000000000000000000000000000000003635c9adc5dea00000" + // amount = 1000e18
|
||||
"0000000000000000000000000000000000000000000000000000000000278d00" + // lockTime
|
||||
"0000000000000000000000000000000000000000000000000000000065f5e100" // ts
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicVLUXDeposit, user, lockEnd},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, vlux, _, _ := ParseDAOEvents(logs)
|
||||
if len(vlux) != 1 {
|
||||
t.Fatalf("expected 1 vLUX deposit, got %d", len(vlux))
|
||||
}
|
||||
v := vlux[0]
|
||||
if v.User != "0xcccccccccccccccccccccccccccccccccccccccc" {
|
||||
t.Errorf("user = %s", v.User)
|
||||
}
|
||||
if v.Event != "deposit" {
|
||||
t.Errorf("event = %s, want deposit", v.Event)
|
||||
}
|
||||
expected := new(big.Int)
|
||||
expected.SetString("3635c9adc5dea00000", 16)
|
||||
if v.Amount.Cmp(expected) != 0 {
|
||||
t.Errorf("amount = %s, want %s", v.Amount, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVLUXWithdraw(t *testing.T) {
|
||||
user := "0x000000000000000000000000dddddddddddddddddddddddddddddddddddddddd"
|
||||
data := "0x" +
|
||||
"0000000000000000000000000000000000000000000000056bc75e2d63100000" + // amount = 100e18
|
||||
"0000000000000000000000000000000000000000000000000000000065f5e100" // ts
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicVLUXWithdraw, user},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, vlux, _, _ := ParseDAOEvents(logs)
|
||||
if len(vlux) != 1 || vlux[0].Event != "withdraw" {
|
||||
t.Fatalf("expected 1 vLUX withdraw")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKarmaMinted(t *testing.T) {
|
||||
to := "0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
|
||||
data := "0x" +
|
||||
"0000000000000000000000000000000000000000000000056bc75e2d63100000" + // amount = 100e18
|
||||
"4449445f564552494649434154494f4e000000000000000000000000000000000" // reason (truncated hash)
|
||||
|
||||
// Fix data to be word-aligned
|
||||
data = "0x" +
|
||||
"0000000000000000000000000000000000000000000000056bc75e2d63100000" + // amount
|
||||
"4449445f564552494649434154494f4e0000000000000000000000000000000000" // reason bytes32
|
||||
|
||||
// Actually: just use clean 2-word data
|
||||
data = "0x" +
|
||||
"0000000000000000000000000000000000000000000000056bc75e2d63100000" +
|
||||
"4449445f564552494649434154494f4e000000000000000000000000000000ff"
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicKarmaMinted, to},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, _, karma, _ := ParseDAOEvents(logs)
|
||||
if len(karma) != 1 {
|
||||
t.Fatalf("expected 1 karma event, got %d", len(karma))
|
||||
}
|
||||
k := karma[0]
|
||||
if k.Event != "minted" {
|
||||
t.Errorf("event = %s, want minted", k.Event)
|
||||
}
|
||||
if k.Account != "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" {
|
||||
t.Errorf("account = %s", k.Account)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseKarmaDecayed(t *testing.T) {
|
||||
account := "0x000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
data := "0x" +
|
||||
"0000000000000000000000000000000000000000000000000de0b6b3a7640000" + // amount = 1e18
|
||||
"0000000000000000000000000000000000000000000000000000000000000001" // wasActive = true
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicKarmaDecayed, account},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, _, karma, _ := ParseDAOEvents(logs)
|
||||
if len(karma) != 1 || karma[0].Event != "decayed" {
|
||||
t.Fatal("expected decayed event")
|
||||
}
|
||||
if !karma[0].WasActive {
|
||||
t.Error("expected wasActive = true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDIDLinked(t *testing.T) {
|
||||
account := "0x000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
did := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicDIDLinked, account, did},
|
||||
Data: "0x",
|
||||
}}
|
||||
|
||||
_, _, karma, _ := ParseDAOEvents(logs)
|
||||
if len(karma) != 1 || karma[0].Event != "did_linked" {
|
||||
t.Fatal("expected did_linked event")
|
||||
}
|
||||
if karma[0].DID != did {
|
||||
t.Errorf("did = %s, want %s", karma[0].DID, did)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDLUXStaked(t *testing.T) {
|
||||
user := "0x000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
data := "0x" +
|
||||
"00000000000000000000000000000000000000000000003635c9adc5dea00000" + // amount = 1000e18
|
||||
"0000000000000000000000000000000000000000000000000000000000000002" + // tier = 2 (Gold)
|
||||
"0000000000000000000000000000000000000000000000000000000065f5e100" // lockEnd
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicDLUXStaked, user},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, _, _, dlux := ParseDAOEvents(logs)
|
||||
if len(dlux) != 1 || dlux[0].Event != "staked" {
|
||||
t.Fatal("expected staked event")
|
||||
}
|
||||
if dlux[0].Tier != 2 {
|
||||
t.Errorf("tier = %d, want 2", dlux[0].Tier)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRebased(t *testing.T) {
|
||||
data := "0x" +
|
||||
"0000000000000000000000000000000000000000000000000000000000000001" + // epoch = 1
|
||||
"00000000000000000000000000000000000000000000003635c9adc5dea00000" + // totalRebased
|
||||
"0000000000000000000000000000000000000000000000000000000000000028" // rate = 40 bps
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicRebased},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, _, _, dlux := ParseDAOEvents(logs)
|
||||
if len(dlux) != 1 || dlux[0].Event != "rebased" {
|
||||
t.Fatal("expected rebased event")
|
||||
}
|
||||
if dlux[0].Epoch.Cmp(big.NewInt(1)) != 0 {
|
||||
t.Errorf("epoch = %s, want 1", dlux[0].Epoch)
|
||||
}
|
||||
if dlux[0].Rate.Cmp(big.NewInt(40)) != 0 {
|
||||
t.Errorf("rate = %s, want 40", dlux[0].Rate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDLUXMinted(t *testing.T) {
|
||||
user := "0x000000000000000000000000cccccccccccccccccccccccccccccccccccccccc"
|
||||
reason := "0xabcdef0000000000000000000000000000000000000000000000000000000000"
|
||||
data := "0x0000000000000000000000000000000000000000000000056bc75e2d63100000" // amount
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicDLUXMinted, user, reason},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, _, _, dlux := ParseDAOEvents(logs)
|
||||
if len(dlux) != 1 || dlux[0].Event != "minted" {
|
||||
t.Fatal("expected minted event")
|
||||
}
|
||||
if dlux[0].Reason != reason {
|
||||
t.Errorf("reason = %s", dlux[0].Reason)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeightsUpdated(t *testing.T) {
|
||||
data := "0x0000000000000000000000000000000000000000000000000000000065f5e100"
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicWeightsUpdated},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
gauges, _, _, _ := ParseDAOEvents(logs)
|
||||
if len(gauges) != 1 || gauges[0].Event != "weights_updated" {
|
||||
t.Fatal("expected weights_updated event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyDAOLogs(t *testing.T) {
|
||||
gauges, vlux, karma, dlux := ParseDAOEvents(nil)
|
||||
if gauges != nil || vlux != nil || karma != nil || dlux != nil {
|
||||
t.Error("expected nil results for nil input")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package defi
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLiquidTopicsLength(t *testing.T) {
|
||||
topics := LiquidTopics()
|
||||
if len(topics) != 4 {
|
||||
t.Fatalf("expected 4 liquid topics, got %d", len(topics))
|
||||
}
|
||||
for _, topic := range topics {
|
||||
if len(topic) != 66 || topic[:2] != "0x" {
|
||||
t.Errorf("invalid topic format: %s", topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFeesReceived(t *testing.T) {
|
||||
from := "0x000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
feeType := "0x4445580000000000000000000000000000000000000000000000000000000000" // keccak256("DEX") prefix
|
||||
data := "0x" +
|
||||
"00000000000000000000000000000000000000000000003635c9adc5dea00000" + // amount = 1000e18
|
||||
"0000000000000000000000000000000000000000000000056bc75e2d63100000" + // perfFee = 100e18
|
||||
"0000000000000000000000000000000000000000000000008ac7230489e80000" // toReserve = 10e18
|
||||
|
||||
logs := []Log{{
|
||||
Address: "0x7777777777777777777777777777777777777777",
|
||||
Topics: []string{TopicFeesReceived, from, feeType},
|
||||
Data: data,
|
||||
BlockNumber: 300,
|
||||
TxHash: "0xccc",
|
||||
}}
|
||||
|
||||
events := ParseLiquidEvents(logs)
|
||||
if len(events) != 1 {
|
||||
t.Fatalf("expected 1 liquid event, got %d", len(events))
|
||||
}
|
||||
|
||||
e := events[0]
|
||||
if e.Event != "fees_received" {
|
||||
t.Errorf("event = %s, want fees_received", e.Event)
|
||||
}
|
||||
if e.From != "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
|
||||
t.Errorf("from = %s", e.From)
|
||||
}
|
||||
if e.FeeType != feeType {
|
||||
t.Errorf("feeType = %s", e.FeeType)
|
||||
}
|
||||
|
||||
expectedAmount := new(big.Int)
|
||||
expectedAmount.SetString("3635c9adc5dea00000", 16)
|
||||
if e.Amount.Cmp(expectedAmount) != 0 {
|
||||
t.Errorf("amount = %s, want %s", e.Amount, expectedAmount)
|
||||
}
|
||||
|
||||
expectedPerfFee := new(big.Int)
|
||||
expectedPerfFee.SetString("56bc75e2d63100000", 16)
|
||||
if e.PerfFee.Cmp(expectedPerfFee) != 0 {
|
||||
t.Errorf("perfFee = %s, want %s", e.PerfFee, expectedPerfFee)
|
||||
}
|
||||
|
||||
expectedReserve := new(big.Int)
|
||||
expectedReserve.SetString("8ac7230489e80000", 16)
|
||||
if e.ToReserve.Cmp(expectedReserve) != 0 {
|
||||
t.Errorf("toReserve = %s, want %s", e.ToReserve, expectedReserve)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseValidatorRewards(t *testing.T) {
|
||||
from := "0x000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
data := "0x0000000000000000000000000000000000000000000000056bc75e2d63100000" // amount = 100e18
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicValidatorRewardsReceived, from},
|
||||
Data: data,
|
||||
BlockNumber: 310,
|
||||
}}
|
||||
|
||||
events := ParseLiquidEvents(logs)
|
||||
if len(events) != 1 || events[0].Event != "validator_rewards" {
|
||||
t.Fatal("expected validator_rewards event")
|
||||
}
|
||||
if events[0].From != "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" {
|
||||
t.Errorf("from = %s", events[0].From)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSlashingApplied(t *testing.T) {
|
||||
data := "0x" +
|
||||
"0000000000000000000000000000000000000000000000056bc75e2d63100000" + // amount = 100e18
|
||||
"000000000000000000000000000000000000000000000000016345785d8a0000" + // fromReserve = 0.1e18
|
||||
"0000000000000000000000000000000000000000000000000de0b6b3a7640000" // socialized = 1e18
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicSlashingApplied},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
events := ParseLiquidEvents(logs)
|
||||
if len(events) != 1 || events[0].Event != "slashing" {
|
||||
t.Fatal("expected slashing event")
|
||||
}
|
||||
|
||||
e := events[0]
|
||||
expectedAmount := new(big.Int)
|
||||
expectedAmount.SetString("56bc75e2d63100000", 16)
|
||||
if e.Amount.Cmp(expectedAmount) != 0 {
|
||||
t.Errorf("amount = %s, want %s", e.Amount, expectedAmount)
|
||||
}
|
||||
if e.FromReserve == nil || e.FromReserve.Sign() == 0 {
|
||||
t.Error("expected non-zero fromReserve")
|
||||
}
|
||||
if e.Socialized == nil || e.Socialized.Sign() == 0 {
|
||||
t.Error("expected non-zero socialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEmergencyWithdrawal(t *testing.T) {
|
||||
to := "0x000000000000000000000000cccccccccccccccccccccccccccccccccccccccc"
|
||||
data := "0x00000000000000000000000000000000000000000000003635c9adc5dea00000"
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicEmergencyWithdrawal, to},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
events := ParseLiquidEvents(logs)
|
||||
if len(events) != 1 || events[0].Event != "emergency_withdrawal" {
|
||||
t.Fatal("expected emergency_withdrawal event")
|
||||
}
|
||||
if events[0].From != "0xcccccccccccccccccccccccccccccccccccccccc" {
|
||||
t.Errorf("from = %s", events[0].From)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingTopicsLiquid(t *testing.T) {
|
||||
// FeesReceived requires 3 topics; test with only 1
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicFeesReceived},
|
||||
Data: "0x",
|
||||
}}
|
||||
events := ParseLiquidEvents(logs)
|
||||
if len(events) != 0 {
|
||||
t.Error("expected no events for insufficient topics")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyLiquidLogs(t *testing.T) {
|
||||
events := ParseLiquidEvents(nil)
|
||||
if events != nil {
|
||||
t.Error("expected nil results for nil input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllTopicsUnique(t *testing.T) {
|
||||
// Collect all topics from all modules and check for duplicates
|
||||
all := make(map[string]string)
|
||||
check := func(name string, topics []string) {
|
||||
for _, topic := range topics {
|
||||
if prev, exists := all[topic]; exists {
|
||||
// Some topics may intentionally collide across modules
|
||||
// (e.g., VoteCast in governance vs gauge controller have different sigs)
|
||||
_ = prev
|
||||
}
|
||||
all[topic] = name
|
||||
}
|
||||
}
|
||||
check("governance", GovernanceTopics())
|
||||
check("dao", DAOTopics())
|
||||
check("treasury", TreasuryTopics())
|
||||
check("liquid", LiquidTopics())
|
||||
|
||||
// Verify all are valid keccak256 hashes
|
||||
for topic, source := range all {
|
||||
if len(topic) != 66 {
|
||||
t.Errorf("%s topic has length %d: %s", source, len(topic), topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package defi
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTreasuryTopicsLength(t *testing.T) {
|
||||
topics := TreasuryTopics()
|
||||
if len(topics) != 8 {
|
||||
t.Fatalf("expected 8 treasury topics, got %d", len(topics))
|
||||
}
|
||||
for _, topic := range topics {
|
||||
if len(topic) != 66 || topic[:2] != "0x" {
|
||||
t.Errorf("invalid topic format: %s", topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVaultReceive(t *testing.T) {
|
||||
chain := "0x0000000000000000000000000000000000000000000000000000000000000001"
|
||||
data := "0x" +
|
||||
"00000000000000000000000000000000000000000000003635c9adc5dea00000" + // amount
|
||||
"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" // warpId
|
||||
|
||||
logs := []Log{{
|
||||
Address: "0x6666666666666666666666666666666666666666",
|
||||
Topics: []string{TopicVaultReceive, chain},
|
||||
Data: data,
|
||||
BlockNumber: 200,
|
||||
TxHash: "0xbbb",
|
||||
}}
|
||||
|
||||
vaults, _, _ := ParseTreasuryEvents(logs)
|
||||
if len(vaults) != 1 {
|
||||
t.Fatalf("expected 1 vault event, got %d", len(vaults))
|
||||
}
|
||||
v := vaults[0]
|
||||
if v.Event != "receive" {
|
||||
t.Errorf("event = %s, want receive", v.Event)
|
||||
}
|
||||
if v.Chain != chain {
|
||||
t.Errorf("chain = %s", v.Chain)
|
||||
}
|
||||
expected := new(big.Int)
|
||||
expected.SetString("3635c9adc5dea00000", 16)
|
||||
if v.Amount.Cmp(expected) != 0 {
|
||||
t.Errorf("amount = %s, want %s", v.Amount, expected)
|
||||
}
|
||||
if v.WarpID == "" || v.WarpID == "0x0" {
|
||||
t.Error("expected non-zero warpId")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVaultFlush(t *testing.T) {
|
||||
chain := "0x0000000000000000000000000000000000000000000000000000000000000002"
|
||||
data := "0x0000000000000000000000000000000000000000000000056bc75e2d63100000" // amount
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicVaultFlush, chain},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
vaults, _, _ := ParseTreasuryEvents(logs)
|
||||
if len(vaults) != 1 || vaults[0].Event != "flush" {
|
||||
t.Fatal("expected flush event")
|
||||
}
|
||||
if vaults[0].Chain != chain {
|
||||
t.Errorf("chain = %s", vaults[0].Chain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFeeGovRate(t *testing.T) {
|
||||
data := "0x" +
|
||||
"000000000000000000000000000000000000000000000000000000000000001e" + // rate = 30 bps
|
||||
"0000000000000000000000000000000000000000000000000000000000000005" // version = 5
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicFeeGovRate},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, feegovs, _ := ParseTreasuryEvents(logs)
|
||||
if len(feegovs) != 1 || feegovs[0].Event != "rate" {
|
||||
t.Fatal("expected rate event")
|
||||
}
|
||||
if feegovs[0].Rate != 30 {
|
||||
t.Errorf("rate = %d, want 30", feegovs[0].Rate)
|
||||
}
|
||||
if feegovs[0].Version != 5 {
|
||||
t.Errorf("version = %d, want 5", feegovs[0].Version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFeeGovChain(t *testing.T) {
|
||||
chainID := "0xabcdef0000000000000000000000000000000000000000000000000000000000"
|
||||
data := "0x0000000000000000000000000000000000000000000000000000000000000001" // active = true
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicFeeGovChain, chainID},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, feegovs, _ := ParseTreasuryEvents(logs)
|
||||
if len(feegovs) != 1 || feegovs[0].Event != "chain" {
|
||||
t.Fatal("expected chain event")
|
||||
}
|
||||
if !feegovs[0].Active {
|
||||
t.Error("expected active = true")
|
||||
}
|
||||
if feegovs[0].ChainID != chainID {
|
||||
t.Errorf("chainID = %s", feegovs[0].ChainID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFeeGovBroadcast(t *testing.T) {
|
||||
data := "0x" +
|
||||
"0000000000000000000000000000000000000000000000000000000000000003" + // version = 3
|
||||
"0000000000000000000000000000000000000000000000000000000000000005" // count = 5
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicFeeGovBroadcast},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, feegovs, _ := ParseTreasuryEvents(logs)
|
||||
if len(feegovs) != 1 || feegovs[0].Event != "broadcast" {
|
||||
t.Fatal("expected broadcast event")
|
||||
}
|
||||
if feegovs[0].Version != 3 {
|
||||
t.Errorf("version = %d, want 3", feegovs[0].Version)
|
||||
}
|
||||
if feegovs[0].Count.Cmp(big.NewInt(5)) != 0 {
|
||||
t.Errorf("count = %s, want 5", feegovs[0].Count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRouterWeight(t *testing.T) {
|
||||
recipient := "0x000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
data := "0x0000000000000000000000000000000000000000000000000000000000001388" // weight = 5000
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicRouterWeight, recipient},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, _, routers := ParseTreasuryEvents(logs)
|
||||
if len(routers) != 1 || routers[0].Event != "weight" {
|
||||
t.Fatal("expected weight event")
|
||||
}
|
||||
if routers[0].Recipient != "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
|
||||
t.Errorf("recipient = %s", routers[0].Recipient)
|
||||
}
|
||||
if routers[0].Weight.Cmp(big.NewInt(5000)) != 0 {
|
||||
t.Errorf("weight = %s, want 5000", routers[0].Weight)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRouterDistribute(t *testing.T) {
|
||||
data := "0x00000000000000000000000000000000000000000000003635c9adc5dea00000"
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicRouterDistribute},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, _, routers := ParseTreasuryEvents(logs)
|
||||
if len(routers) != 1 || routers[0].Event != "distribute" {
|
||||
t.Fatal("expected distribute event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRouterClaim(t *testing.T) {
|
||||
recipient := "0x000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
data := "0x0000000000000000000000000000000000000000000000056bc75e2d63100000" // amount
|
||||
|
||||
logs := []Log{{
|
||||
Topics: []string{TopicRouterClaim, recipient},
|
||||
Data: data,
|
||||
}}
|
||||
|
||||
_, _, routers := ParseTreasuryEvents(logs)
|
||||
if len(routers) != 1 || routers[0].Event != "claim" {
|
||||
t.Fatal("expected claim event")
|
||||
}
|
||||
if routers[0].Recipient != "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" {
|
||||
t.Errorf("recipient = %s", routers[0].Recipient)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyTreasuryLogs(t *testing.T) {
|
||||
vaults, feegovs, routers := ParseTreasuryEvents(nil)
|
||||
if vaults != nil || feegovs != nil || routers != nil {
|
||||
t.Error("expected nil results for nil input")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Package liquidity provides resolvers for the Liquidity Protocol (omnichain DeFi).
|
||||
//
|
||||
// Indexes: cross-chain swaps, limit orders, arbitrage, OMA router, oracle prices,
|
||||
// strategy orders, market making, bridge operations.
|
||||
//
|
||||
// Entities: CrossChainSwap, LimitOrder, Arbitrage, OMAPool, OMASwap, PriceUpdate,
|
||||
// StrategyOrder, MarketMaker, BridgeTransfer, LiquidityStats
|
||||
package liquidity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
||||
|
||||
func Register(resolvers map[string]ResolverFunc) {
|
||||
resolvers["crossChainSwap"] = resolveSingle("CrossChainSwap")
|
||||
resolvers["crossChainSwaps"] = resolveList("CrossChainSwap")
|
||||
resolvers["limitOrder"] = resolveSingle("LimitOrder")
|
||||
resolvers["limitOrders"] = resolveList("LimitOrder")
|
||||
resolvers["arbitrage"] = resolveSingle("Arbitrage")
|
||||
resolvers["arbitrages"] = resolveList("Arbitrage")
|
||||
resolvers["omaPool"] = resolveSingle("OMAPool")
|
||||
resolvers["omaPools"] = resolveList("OMAPool")
|
||||
resolvers["omaSwap"] = resolveSingle("OMASwap")
|
||||
resolvers["omaSwaps"] = resolveList("OMASwap")
|
||||
resolvers["priceUpdate"] = resolveSingle("PriceUpdate")
|
||||
resolvers["priceUpdates"] = resolveList("PriceUpdate")
|
||||
resolvers["strategyOrder"] = resolveSingle("StrategyOrder")
|
||||
resolvers["strategyOrders"] = resolveList("StrategyOrder")
|
||||
resolvers["marketMaker"] = resolveSingle("MarketMaker")
|
||||
resolvers["marketMakers"] = resolveList("MarketMaker")
|
||||
resolvers["bridgeTransfer"] = resolveSingle("BridgeTransfer")
|
||||
resolvers["bridgeTransfers"] = resolveList("BridgeTransfer")
|
||||
resolvers["liquidityStats"] = resolveStats
|
||||
}
|
||||
|
||||
func resolveSingle(typeName string) ResolverFunc {
|
||||
return func(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok {
|
||||
return s.GetByType(typeName, fmt.Sprint(id))
|
||||
}
|
||||
return nil, fmt.Errorf("%s requires id", typeName)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveList(typeName string) ResolverFunc {
|
||||
return func(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType(typeName, pl(args))
|
||||
}
|
||||
}
|
||||
|
||||
func resolveStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
||||
return s.GetByType("LiquidityStats", "1")
|
||||
}
|
||||
|
||||
func pl(args map[string]interface{}) int {
|
||||
limit := 100
|
||||
if l, ok := args["first"]; ok {
|
||||
fmt.Sscanf(fmt.Sprint(l), "%d", &limit)
|
||||
}
|
||||
return limit
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Package liquidprotocol provides resolvers for the Liquid Protocol (liquid staking + wrapped assets).
|
||||
//
|
||||
// Indexes: fees, validator rewards, slashing, teleporter deposits/yields/withdrawals,
|
||||
// vault strategies, backing attestations.
|
||||
//
|
||||
// Entities: LiquidFee, ValidatorReward, SlashingEvent, DepositMint, YieldMint,
|
||||
// WithdrawBurn, BackingUpdate, MPCOracle, VaultStrategy, YieldHarvest, LiquidProtocolStats
|
||||
package liquidprotocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/graph/storage"
|
||||
)
|
||||
|
||||
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
||||
|
||||
func Register(resolvers map[string]ResolverFunc) {
|
||||
resolvers["liquidFee"] = resolveSingle("LiquidFee")
|
||||
resolvers["liquidFees"] = resolveList("LiquidFee")
|
||||
resolvers["validatorReward"] = resolveSingle("ValidatorReward")
|
||||
resolvers["validatorRewards"] = resolveList("ValidatorReward")
|
||||
resolvers["slashingEvent"] = resolveSingle("SlashingEvent")
|
||||
resolvers["slashingEvents"] = resolveList("SlashingEvent")
|
||||
resolvers["depositMint"] = resolveSingle("DepositMint")
|
||||
resolvers["depositMints"] = resolveList("DepositMint")
|
||||
resolvers["yieldMint"] = resolveSingle("YieldMint")
|
||||
resolvers["yieldMints"] = resolveList("YieldMint")
|
||||
resolvers["withdrawBurn"] = resolveSingle("WithdrawBurn")
|
||||
resolvers["withdrawBurns"] = resolveList("WithdrawBurn")
|
||||
resolvers["backingUpdate"] = resolveSingle("BackingUpdate")
|
||||
resolvers["backingUpdates"] = resolveList("BackingUpdate")
|
||||
resolvers["vaultStrategy"] = resolveSingle("VaultStrategy")
|
||||
resolvers["vaultStrategies"] = resolveList("VaultStrategy")
|
||||
resolvers["yieldHarvest"] = resolveSingle("YieldHarvest")
|
||||
resolvers["yieldHarvests"] = resolveList("YieldHarvest")
|
||||
resolvers["liquidProtocolStats"] = resolveStats
|
||||
}
|
||||
|
||||
func resolveSingle(typeName string) ResolverFunc {
|
||||
return func(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
if id, ok := args["id"]; ok {
|
||||
return s.GetByType(typeName, fmt.Sprint(id))
|
||||
}
|
||||
return nil, fmt.Errorf("%s requires id", typeName)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveList(typeName string) ResolverFunc {
|
||||
return func(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
||||
return s.ListByType(typeName, pl(args))
|
||||
}
|
||||
}
|
||||
|
||||
func resolveStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
||||
return s.GetByType("LiquidProtocolStats", "1")
|
||||
}
|
||||
|
||||
func pl(args map[string]interface{}) int {
|
||||
limit := 100
|
||||
if l, ok := args["first"]; ok {
|
||||
fmt.Sscanf(fmt.Sprint(l), "%d", &limit)
|
||||
}
|
||||
return limit
|
||||
}
|
||||
Reference in New Issue
Block a user