From 0221d581244460ac97d3f620fd55771fabbaae87 Mon Sep 17 00:00:00 2001 From: Soumya Ghosh Dastidar Date: Thu, 1 Jul 2021 11:15:51 +0530 Subject: [PATCH 1/3] added multichain apis for arbitrum Signed-off-by: Soumya Ghosh Dastidar --- chain/arbitrum/address.go | 33 +++++ chain/arbitrum/address_test.go | 105 ++++++++++++++ chain/arbitrum/client.go | 17 +++ chain/arbitrum/encode.go | 11 ++ chain/arbitrum/encode_test.go | 255 +++++++++++++++++++++++++++++++++ chain/arbitrum/gas.go | 11 ++ chain/arbitrum/tx.go | 16 +++ 7 files changed, 448 insertions(+) create mode 100644 chain/arbitrum/address.go create mode 100644 chain/arbitrum/address_test.go create mode 100644 chain/arbitrum/client.go create mode 100644 chain/arbitrum/encode.go create mode 100644 chain/arbitrum/encode_test.go create mode 100644 chain/arbitrum/gas.go create mode 100644 chain/arbitrum/tx.go diff --git a/chain/arbitrum/address.go b/chain/arbitrum/address.go new file mode 100644 index 00000000..c5708fa7 --- /dev/null +++ b/chain/arbitrum/address.go @@ -0,0 +1,33 @@ +package arbitrum + +import ( + "github.com/renproject/multichain/chain/ethereum" +) + +type ( + // AddressEncodeDecoder re-exports ethereum.AddressEncodeDecoder. + AddressEncodeDecoder = ethereum.AddressEncodeDecoder + + // AddressEncoder re-exports ethereum.AddressEncoder. + AddressEncoder = ethereum.AddressEncoder + + // AddressDecoder re-exports ethereum.AddressDecoder. + AddressDecoder = ethereum.AddressDecoder + + // Address re-exports ethereum.Address. + Address = ethereum.Address +) + +var ( + // NewAddressEncodeDecoder re-exports ethereum.NewAddressEncodeDecoder. + NewAddressEncodeDecoder = ethereum.NewAddressEncodeDecoder + + // NewAddressDecoder re-exports ethereum.NewAddressDecoder. + NewAddressDecoder = ethereum.NewAddressDecoder + + // NewAddressEncoder re-exports ethereum.NewAddressEncoder. + NewAddressEncoder = ethereum.NewAddressEncoder + + // NewAddressFromHex re-exports ethereum.NewAddressFromHex. + NewAddressFromHex = ethereum.NewAddressFromHex +) diff --git a/chain/arbitrum/address_test.go b/chain/arbitrum/address_test.go new file mode 100644 index 00000000..70793658 --- /dev/null +++ b/chain/arbitrum/address_test.go @@ -0,0 +1,105 @@ +package arbitrum_test + +import ( + "encoding/hex" + "encoding/json" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/renproject/multichain/chain/arbitrum" + "github.com/renproject/surge" + "testing/quick" +) + +var _ = Describe("Address", func() { + Context("when unmarshaling and unmarshaling", func() { + It("should equal itself", func() { + f := func(x [20]byte) bool { + addr := arbitrum.Address(x) + Expect(addr.SizeHint()).To(Equal(20)) + + bytes, err := surge.ToBinary(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr arbitrum.Address + err = surge.FromBinary(&newAddr, bytes) + Expect(err).ToNot(HaveOccurred()) + + Expect(addr).To(Equal(newAddr)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when unmarshaling and unmarshaling to/from JSON", func() { + It("should equal itself", func() { + f := func(x [20]byte) bool { + addr := arbitrum.Address(x) + + bytes, err := json.Marshal(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr arbitrum.Address + err = json.Unmarshal(bytes, &newAddr) + Expect(err).ToNot(HaveOccurred()) + + Expect(addr).To(Equal(newAddr)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + + Context("when the address is invalid hex", func() { + It("should return an error", func() { + f := func(x [40]byte) bool { + bytes, err := json.Marshal(string(x[:])) + Expect(err).ToNot(HaveOccurred()) + + var newAddr arbitrum.Address + err = json.Unmarshal(bytes, &newAddr) + Expect(err).To(HaveOccurred()) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when the address is invalid length", func() { + It("should return an error", func() { + f := func(x [10]byte) bool { + addr := hex.EncodeToString(x[:]) + bytes, err := json.Marshal(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr arbitrum.Address + err = json.Unmarshal(bytes, &newAddr) + Expect(err).To(HaveOccurred()) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + }) + + Context("when unmarshalling random data", func() { + It("should not panic", func() { + f := func(x []byte) bool { + var addr arbitrum.Address + Expect(func() { addr.Unmarshal(x, surge.MaxBytes) }).ToNot(Panic()) + Expect(func() { json.Unmarshal(x, &addr) }).ToNot(Panic()) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) +}) diff --git a/chain/arbitrum/client.go b/chain/arbitrum/client.go new file mode 100644 index 00000000..c3eee309 --- /dev/null +++ b/chain/arbitrum/client.go @@ -0,0 +1,17 @@ +package arbitrum + +import ( + "github.com/renproject/multichain/chain/ethereum" +) + +const ( + // DefaultClientRPCURL is the RPC URL used by default, to interact with the + // arbitrum node. + DefaultClientRPCURL = "http://127.0.0.1:8547" +) + +// Client re-exports ethereum.Client. +type Client = ethereum.Client + +// NewClient re-exports ethereum.NewClient. +var NewClient = ethereum.NewClient diff --git a/chain/arbitrum/encode.go b/chain/arbitrum/encode.go new file mode 100644 index 00000000..96f9cce0 --- /dev/null +++ b/chain/arbitrum/encode.go @@ -0,0 +1,11 @@ +package arbitrum + +import ( + "github.com/renproject/multichain/chain/ethereum" +) + +// Payload re-exports ethereum.Payload. +type Payload = ethereum.Payload + +// Encode re-exports ethereum.Encode. +var Encode = ethereum.Encode diff --git a/chain/arbitrum/encode_test.go b/chain/arbitrum/encode_test.go new file mode 100644 index 00000000..53b67563 --- /dev/null +++ b/chain/arbitrum/encode_test.go @@ -0,0 +1,255 @@ +package arbitrum_test + +import ( + "encoding/hex" + "fmt" + "github.com/renproject/multichain/chain/arbitrum" + "math" + "testing/quick" + + "github.com/renproject/pack" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("Encoding", func() { + Context("when encoding bytes", func() { + It("should return the correct result", func() { + f := func(x []byte) bool { + arg := pack.NewBytes(x) + + resBytes := arbitrum.Encode(arg) + resString := hex.EncodeToString(resBytes) + + expectedBytes := make([]byte, int(math.Ceil(float64(len(x))/32)*32)) + copy(expectedBytes, x) + // Note: since the first parameter has a dynamic length, the + // first 32 bytes instead contain a pointer to the data. + expectedString := fmt.Sprintf("%064x", 32) + fmt.Sprintf("%064x", len(x)) + hex.EncodeToString(expectedBytes) + + Expect(resString).To(Equal(expectedString)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when encoding 32 bytes", func() { + It("should return the correct result", func() { + f := func(x [32]byte) bool { + arg := pack.NewBytes32(x) + + resBytes := arbitrum.Encode(arg) + resString := hex.EncodeToString(resBytes) + expectedString := hex.EncodeToString(x[:]) + + Expect(resString).To(Equal(expectedString)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when encoding 8-bit unsigned integers", func() { + It("should return the correct result", func() { + f := func(x uint8) bool { + arg := pack.NewU8(x) + + resBytes := arbitrum.Encode(arg) + resString := hex.EncodeToString(resBytes) + expectedString := fmt.Sprintf("%064x", x) + + Expect(resString).To(Equal(expectedString)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when encoding 16-bit unsigned integers", func() { + It("should return the correct result", func() { + f := func(x uint16) bool { + arg := pack.NewU16(x) + + resBytes := arbitrum.Encode(arg) + resString := hex.EncodeToString(resBytes) + expectedString := fmt.Sprintf("%064x", x) + + Expect(resString).To(Equal(expectedString)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when encoding 32-bit unsigned integers", func() { + It("should return the correct result", func() { + f := func(x uint32) bool { + arg := pack.NewU32(x) + + resBytes := arbitrum.Encode(arg) + resString := hex.EncodeToString(resBytes) + expectedString := fmt.Sprintf("%064x", x) + + Expect(resString).To(Equal(expectedString)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when encoding 64-bit unsigned integers", func() { + It("should return the correct result", func() { + f := func(x uint64) bool { + arg := pack.NewU64(x) + + resBytes := arbitrum.Encode(arg) + resString := hex.EncodeToString(resBytes) + expectedString := fmt.Sprintf("%064x", x) + + Expect(resString).To(Equal(expectedString)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when encoding 128-bit unsigned integers", func() { + It("should return the correct result", func() { + f := func(x [16]byte) bool { + arg := pack.NewU128(x) + + resBytes := arbitrum.Encode(arg) + resString := hex.EncodeToString(resBytes) + expectedString := fmt.Sprintf("%064x", x) + + Expect(resString).To(Equal(expectedString)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when encoding 256-bit unsigned integers", func() { + It("should return the correct result", func() { + f := func(x [32]byte) bool { + arg := pack.NewU256(x) + + resBytes := arbitrum.Encode(arg) + resString := hex.EncodeToString(resBytes) + expectedString := fmt.Sprintf("%064x", x) + + Expect(resString).To(Equal(expectedString)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when encoding Ethereum addresses", func() { + It("should return the correct result", func() { + f := func(x [20]byte) bool { + arg := arbitrum.Address(x) + + resBytes := arbitrum.Encode(arg) + resString := hex.EncodeToString(resBytes) + + expectedBytes := make([]byte, 32) + copy(expectedBytes, x[:]) + expectedString := hex.EncodeToString(expectedBytes) + + Expect(resString).To(Equal(expectedString)) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("when encoding an unsupported type", func() { + It("should panic", func() { + f := func(x bool) bool { + arg := pack.NewBool(x) + Expect(func() { arbitrum.Encode(arg) }).To(Panic()) + return true + } + + err := quick.Check(f, nil) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + type testCase struct { + addr string + amount uint64 + hash string + result string + } + + testCases := []testCase{ + { + addr: "797522Fb74d42bB9fbF6b76dEa24D01A538d5D66", + amount: 10000, + hash: "702826c3977ee72158db2ce1fb758075ee2799db65fb27b5d0952f860a8084ed", + result: "797522fb74d42bb9fbf6b76dea24d01a538d5d660000000000000000000000000000000000000000000000000000000000000000000000000000000000002710702826c3977ee72158db2ce1fb758075ee2799db65fb27b5d0952f860a8084ed", + }, + { + addr: "58afb504ef2444a267b8c7ce57279417f1377ceb", + amount: 50000000000000000, + hash: "dabff9ceb1b3dabb696d143326fdb98a8c7deb260e65d08a294b16659d573f93", + result: "58afb504ef2444a267b8c7ce57279417f1377ceb00000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec50000dabff9ceb1b3dabb696d143326fdb98a8c7deb260e65d08a294b16659d573f93", + }, + { + addr: "0000000000000000000000000000000000000000", + amount: 0, + hash: "0000000000000000000000000000000000000000000000000000000000000000", + result: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + } + + DescribeTable("when encoding args", + func(test testCase) { + addrBytes, err := hex.DecodeString(test.addr) + Expect(err).ToNot(HaveOccurred()) + + var addr arbitrum.Address + copy(addr[:], addrBytes) + + hashBytes32 := [32]byte{} + hashBytes, err := hex.DecodeString(test.hash) + Expect(err).ToNot(HaveOccurred()) + copy(hashBytes32[:], hashBytes) + + args := []interface{}{ + addr, + pack.NewU64(test.amount), + pack.NewBytes32(hashBytes32), + } + result := arbitrum.Encode(args...) + Expect(hex.EncodeToString(result)).To(Equal(test.result)) + }, + + Entry("should return the same result as solidity for small transactions", testCases[0]), + Entry("should return the same result as solidity for large transactions", testCases[1]), + Entry("should return the same result as solidity for empty transactions", testCases[2]), + ) +}) diff --git a/chain/arbitrum/gas.go b/chain/arbitrum/gas.go new file mode 100644 index 00000000..6e209dd8 --- /dev/null +++ b/chain/arbitrum/gas.go @@ -0,0 +1,11 @@ +package arbitrum + +import ( + "github.com/renproject/multichain/chain/ethereum" +) + +// GasEstimator re-exports ethereum.GasEstimator. +type GasEstimator = ethereum.GasEstimator + +// NewGasEstimator re-exports ethereum.NewGasEstimator. +var NewGasEstimator = ethereum.NewGasEstimator diff --git a/chain/arbitrum/tx.go b/chain/arbitrum/tx.go new file mode 100644 index 00000000..f1367563 --- /dev/null +++ b/chain/arbitrum/tx.go @@ -0,0 +1,16 @@ +package arbitrum + +import ( + "github.com/renproject/multichain/chain/ethereum" +) + +type ( + // TxBuilder re-exports ethereum.TxBuilder. + TxBuilder = ethereum.TxBuilder + + // Tx re-exports ethereum.Tx. + Tx = ethereum.Tx +) + +// NewTxBuilder re-exports ethereum.NewTxBuilder. +var NewTxBuilder = ethereum.NewTxBuilder From 8ad9b362b43373d21c6b3cc65a8d705578ec56ff Mon Sep 17 00:00:00 2001 From: Soumya Ghosh Dastidar Date: Thu, 1 Jul 2021 12:16:29 +0530 Subject: [PATCH 2/3] added arbitrum to multichain.Chain Signed-off-by: Soumya Ghosh Dastidar --- multichain.go | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/multichain.go b/multichain.go index ef2eee7e..4ca7ba77 100644 --- a/multichain.go +++ b/multichain.go @@ -99,20 +99,21 @@ type Asset string // from an existing chain, you must add a human-readable string to this set of // enumerated values. Assets must be listed in alphabetical order. const ( - AVAX = Asset("AVAX") // Avalanche - BCH = Asset("BCH") // Bitcoin Cash - BNB = Asset("BNB") // Binance Coin - BTC = Asset("BTC") // Bitcoin - DGB = Asset("DGB") // DigiByte - DOGE = Asset("DOGE") // Dogecoin - ETH = Asset("ETH") // Ether - FIL = Asset("FIL") // Filecoin - FTM = Asset("FTM") // Fantom - GLMR = Asset("GLMR") // Glimmer - LUNA = Asset("LUNA") // Luna - MATIC = Asset("MATIC") // Matic PoS (Polygon) - SOL = Asset("SOL") // Solana - ZEC = Asset("ZEC") // Zcash + AVAX = Asset("AVAX") // Avalanche + BCH = Asset("BCH") // Bitcoin Cash + BNB = Asset("BNB") // Binance Coin + BTC = Asset("BTC") // Bitcoin + DGB = Asset("DGB") // DigiByte + DOGE = Asset("DOGE") // Dogecoin + ETH = Asset("ETH") // Ether + ARB_ETH = Asset("ARB_ETH") // Arbitrum Ether + FIL = Asset("FIL") // Filecoin + FTM = Asset("FTM") // Fantom + GLMR = Asset("GLMR") // Glimmer + LUNA = Asset("LUNA") // Luna + MATIC = Asset("MATIC") // Matic PoS (Polygon) + SOL = Asset("SOL") // Solana + ZEC = Asset("ZEC") // Zcash // These assets are defined separately because they are mock assets. These // assets should only be used for testing. @@ -154,6 +155,8 @@ func (asset Asset) OriginChain() Chain { return Solana case ZEC: return Zcash + case ARB_ETH: + return Arbitrum // These assets are handled separately because they are mock assets. These // assets should only be used for testing. @@ -175,7 +178,7 @@ func (asset Asset) ChainType() ChainType { switch asset { case BCH, BTC, DGB, DOGE, ZEC: return ChainTypeUTXOBased - case AVAX, BNB, ETH, FIL, GLMR, LUNA, MATIC: + case AVAX, BNB, ETH, FIL, GLMR, LUNA, MATIC, ARB_ETH: return ChainTypeAccountBased // These assets are handled separately because they are mock assets. These @@ -215,6 +218,7 @@ type Chain string // in alphabetical order. const ( Avalanche = Chain("Avalanche") + Arbitrum = Chain("Arbitrum") BinanceSmartChain = Chain("BinanceSmartChain") Bitcoin = Chain("Bitcoin") BitcoinCash = Chain("BitcoinCash") @@ -261,7 +265,7 @@ func (chain Chain) ChainType() ChainType { switch chain { case Bitcoin, BitcoinCash, DigiByte, Dogecoin, Zcash: return ChainTypeUTXOBased - case Avalanche, BinanceSmartChain, Ethereum, Fantom, Filecoin, Moonbeam, Polygon, Solana, Terra: + case Avalanche, BinanceSmartChain, Ethereum, Arbitrum, Fantom, Filecoin, Moonbeam, Polygon, Solana, Terra: return ChainTypeAccountBased // These chains are handled separately because they are mock chains. These @@ -321,6 +325,8 @@ func (chain Chain) NativeAsset() Asset { return LUNA case Zcash: return ZEC + case Arbitrum: + return ARB_ETH // These chains are handled separately because they are mock chains. These // chains should only be used for testing. From 44d7a5caa202c62d306485e94e4cd9700a40b2a7 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Thu, 1 Jul 2021 15:47:48 +0400 Subject: [PATCH 3/3] fix: linting and add check in the declarations test --- multichain.go | 36 ++++++++++++++++++------------------ multichain_test.go | 4 ++++ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/multichain.go b/multichain.go index 4ca7ba77..67286fac 100644 --- a/multichain.go +++ b/multichain.go @@ -99,21 +99,21 @@ type Asset string // from an existing chain, you must add a human-readable string to this set of // enumerated values. Assets must be listed in alphabetical order. const ( - AVAX = Asset("AVAX") // Avalanche - BCH = Asset("BCH") // Bitcoin Cash - BNB = Asset("BNB") // Binance Coin - BTC = Asset("BTC") // Bitcoin - DGB = Asset("DGB") // DigiByte - DOGE = Asset("DOGE") // Dogecoin - ETH = Asset("ETH") // Ether - ARB_ETH = Asset("ARB_ETH") // Arbitrum Ether - FIL = Asset("FIL") // Filecoin - FTM = Asset("FTM") // Fantom - GLMR = Asset("GLMR") // Glimmer - LUNA = Asset("LUNA") // Luna - MATIC = Asset("MATIC") // Matic PoS (Polygon) - SOL = Asset("SOL") // Solana - ZEC = Asset("ZEC") // Zcash + AVAX = Asset("AVAX") // Avalanche + BCH = Asset("BCH") // Bitcoin Cash + BNB = Asset("BNB") // Binance Coin + BTC = Asset("BTC") // Bitcoin + DGB = Asset("DGB") // DigiByte + DOGE = Asset("DOGE") // Dogecoin + ETH = Asset("ETH") // Ether + ArbETH = Asset("ArbETH") // Arbitrum Ether + FIL = Asset("FIL") // Filecoin + FTM = Asset("FTM") // Fantom + GLMR = Asset("GLMR") // Glimmer + LUNA = Asset("LUNA") // Luna + MATIC = Asset("MATIC") // Matic PoS (Polygon) + SOL = Asset("SOL") // Solana + ZEC = Asset("ZEC") // Zcash // These assets are defined separately because they are mock assets. These // assets should only be used for testing. @@ -155,7 +155,7 @@ func (asset Asset) OriginChain() Chain { return Solana case ZEC: return Zcash - case ARB_ETH: + case ArbETH: return Arbitrum // These assets are handled separately because they are mock assets. These @@ -178,7 +178,7 @@ func (asset Asset) ChainType() ChainType { switch asset { case BCH, BTC, DGB, DOGE, ZEC: return ChainTypeUTXOBased - case AVAX, BNB, ETH, FIL, GLMR, LUNA, MATIC, ARB_ETH: + case AVAX, BNB, ETH, FIL, GLMR, LUNA, MATIC, ArbETH: return ChainTypeAccountBased // These assets are handled separately because they are mock assets. These @@ -326,7 +326,7 @@ func (chain Chain) NativeAsset() Asset { case Zcash: return ZEC case Arbitrum: - return ARB_ETH + return ArbETH // These chains are handled separately because they are mock chains. These // chains should only be used for testing. diff --git a/multichain_test.go b/multichain_test.go index 57799b92..d502b736 100644 --- a/multichain_test.go +++ b/multichain_test.go @@ -105,6 +105,10 @@ var _ = Describe("Multichain", func() { chain multichain.Chain asset multichain.Asset }{ + { + multichain.Arbitrum, + multichain.ArbETH, + }, { multichain.Avalanche, multichain.AVAX,