diff --git a/abi/loopring.json b/abi/loopring.json new file mode 100644 index 0000000..2678607 --- /dev/null +++ b/abi/loopring.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"_exchange","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint96","name":"amount","type":"uint96"},{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"Deposited","type":"event"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint96","name":"amount","type":"uint96"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"exchange","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] diff --git a/adapters/contracts/loopring/loopring.go b/adapters/contracts/loopring/loopring.go new file mode 100644 index 0000000..2e228b5 --- /dev/null +++ b/adapters/contracts/loopring/loopring.go @@ -0,0 +1,17 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package loopring + +import ( + "github.com/ethereum/go-ethereum/accounts/abi/bind" +) + + +// LoopringMetaData contains all meta data concerning the Drips contract. +var LoopringMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_exchange\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"duration\",\"type\":\"uint256\"}],\"name\":\"Deposited\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddress\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"},{\"internalType\":\"uint256\",\"name\":\"duration\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"exchange\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", +} + +// LoopringABI is the input ABI used to generate the binding from. +var LoopringABI = LoopringMetaData.ABI diff --git a/adapters/projects/loopring/lock.go b/adapters/projects/loopring/lock.go new file mode 100644 index 0000000..815a5a6 --- /dev/null +++ b/adapters/projects/loopring/lock.go @@ -0,0 +1,96 @@ +package loopring + +import ( + "context" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/taikoxyz/trailblazer-adapters/adapters" + "github.com/taikoxyz/trailblazer-adapters/adapters/contracts/loopring" +) + +const ( + // https://taikoscan.io/address/0xaD32A362645Ac9139CFb5Ba3A2A46fC4c378812B + LockAddress string = "0xaD32A362645Ac9139CFb5Ba3A2A46fC4c378812B" + + logDepositWithDurationSignature string = "Deposited(address,address,address,uint96,uint256)" +) + +type LockIndexer struct { + client *ethclient.Client + addresses []common.Address +} + +func NewLockIndexer(client *ethclient.Client, addresses []common.Address) *LockIndexer { + return &LockIndexer{ + client: client, + addresses: addresses, + } +} + +var _ adapters.LogIndexer[adapters.Lock] = &LockIndexer{} + +func (indexer *LockIndexer) Addresses() []common.Address { + return indexer.addresses +} + +func (indexer *LockIndexer) Index(ctx context.Context, logs ...types.Log) ([]adapters.Lock, error) { + var locks []adapters.Lock + + for _, l := range logs { + if !indexer.isDepositWithDuration(l) { + continue + } + + var depositWithDurationEvent struct { + From common.Address + To common.Address + Token common.Address + Amount *big.Int + Duration *big.Int + } + + loopringABI, err := abi.JSON(strings.NewReader(loopring.LoopringABI)) + if err != nil { + return nil, err + } + + err = loopringABI.UnpackIntoInterface(&depositWithDurationEvent, "Deposited", l.Data) + if err != nil { + return nil, err + } + + if depositWithDurationEvent.Token != common.HexToAddress(adapters.TaikoTokenAddress) { + continue + } + + block, err := indexer.client.BlockByNumber(ctx, big.NewInt(int64(l.BlockNumber))) + if err != nil { + return nil, err + } + + lock := &adapters.Lock{ + User: depositWithDurationEvent.To, + TokenAmount: depositWithDurationEvent.Amount, + TokenDecimals: adapters.TaikoTokenDecimals, + Token: common.HexToAddress(adapters.TaikoTokenAddress), + Duration: depositWithDurationEvent.Duration.Uint64(), + BlockTime: block.Time(), + BlockNumber: block.NumberU64(), + TxHash: l.TxHash, + } + + locks = append(locks, *lock) + } + + return locks, nil +} + +func (indexer *LockIndexer) isDepositWithDuration(l types.Log) bool { + return l.Topics[0].Hex() == crypto.Keccak256Hash([]byte(logDepositWithDurationSignature)).Hex() +} diff --git a/adapters/projects/loopring/lock_test.go b/adapters/projects/loopring/lock_test.go new file mode 100644 index 0000000..4afc787 --- /dev/null +++ b/adapters/projects/loopring/lock_test.go @@ -0,0 +1,41 @@ +package loopring_test + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/taikoxyz/trailblazer-adapters/adapters" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/loopring" +) + +func TestLockIndexer(t *testing.T) { + taikoRPC := "https://rpc.taiko.xyz" + blocknumber := int64(496553) + + ctx := context.Background() + + client, err := ethclient.Dial(taikoRPC) + require.NoError(t, err) + + indexer := loopring.NewLockIndexer(client, []common.Address{common.HexToAddress(loopring.LockAddress)}) + + logs, err := adapters.GetLogs(ctx, client, indexer.Addresses(), blocknumber) + require.NoError(t, err) + + locks, err := indexer.Index(ctx, logs...) + assert.NoError(t, err) + assert.Len(t, locks, 1) + assert.Equal(t, common.HexToAddress("0x6b1029C9AE8Aa5EEA9e045E8ba3C93d380D5BDDa"), locks[0].User) + assert.Equal(t, big.NewInt(1000000000000000000), locks[0].TokenAmount) + assert.Equal(t, adapters.TaikoTokenDecimals, locks[0].TokenDecimals) + assert.Equal(t, common.HexToAddress(adapters.TaikoTokenAddress), locks[0].Token) + assert.Equal(t, uint64(1730112839), locks[0].BlockTime) + assert.Equal(t, uint64(259200), locks[0].Duration) + assert.Equal(t, uint64(blocknumber), locks[0].BlockNumber) + assert.Equal(t, common.HexToHash("0x604cc9466718ffd00787cbe7b0aefaa3b9ab5da030e59b60e6dec4fa31922ad9"), locks[0].TxHash) +} diff --git a/cmd/adapter.go b/cmd/adapter.go index 432d291..372e7bf 100644 --- a/cmd/adapter.go +++ b/cmd/adapter.go @@ -21,6 +21,7 @@ const ( DripsLock adapter = "DripsLock" SymmetricLock adapter = "SymmetricLock" RobinosPrediction adapter = "RobinosPrediction" + LoopringLock adapter = "LoopringLock" ) func adapterz() []adapter { @@ -39,5 +40,6 @@ func adapterz() []adapter { DripsLock, SymmetricLock, RobinosPrediction, + LoopringLock, } } diff --git a/cmd/cmd.go b/cmd/cmd.go index 93b8355..c8ef979 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -25,6 +25,7 @@ import ( "github.com/taikoxyz/trailblazer-adapters/adapters/projects/ritsu" "github.com/taikoxyz/trailblazer-adapters/adapters/projects/symmetric" "github.com/taikoxyz/trailblazer-adapters/adapters/projects/robinos" + "github.com/taikoxyz/trailblazer-adapters/adapters/projects/loopring" transactionsender "github.com/taikoxyz/trailblazer-adapters/adapters/transaction_sender" ) @@ -187,6 +188,12 @@ func executeCommand(p prompt) error { robinos.SelectedMultiplierEvents, ) return processLog(ctx, client, indexer, p.Blocknumber) + case LoopringLock: + indexer := loopring.NewLockIndexer( + client, + []common.Address{common.HexToAddress(loopring.LockAddress)}, + ) + return processLog(ctx, client, indexer, p.Blocknumber) default: return fmt.Errorf("adapter %s is not supported", p.Adapter) diff --git a/whitelist/protocols.json b/whitelist/protocols.json index 800bbc5..59bff3c 100644 --- a/whitelist/protocols.json +++ b/whitelist/protocols.json @@ -736,7 +736,8 @@ "logo": "loopring_logo.png", "contracts": [ "0xbD787F374198d29E2F8Fa228c778FE39e1a5d3a9", - "0x3e71a41325e1d6B450307b6535EC48627ac4DaCC" + "0x3e71a41325e1d6B450307b6535EC48627ac4DaCC", + "0xaD32A362645Ac9139CFb5Ba3A2A46fC4c378812B" ] }, {