diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d14cf7f3..954a7590 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -736,7 +736,498 @@ jobs: -btc=true \ -bch=true \ -timeout 1500s + test-avax: + runs-on: ubuntu-latest + env: + FILECOIN_FFI_COMMIT: 8b97bd8230b77bd32f4f27e4766a6d8a03b4e801 + SOLANA_FFI_COMMIT: 1f85d47b5331a2146834bbd28a51654134aedd7d + steps: + - name: Set up Go 1.13 + uses: actions/setup-go@v1 + with: + go-version: 1.13 + id: go + + - name: Configure git for Private Modules + env: + TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + run: git config --global url."https://roynalnaruto:${TOKEN}@github.com".insteadOf "https://github.com" + + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + with: + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + submodules: recursive + + - name: Caching modules + uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-aw-${{ hashFiles('**/go.sum') }} + + - name: Cache extern dependencies (FFI) + id: cache-extern + uses: actions/cache@v2 + env: + cache-name: cache-externs + with: + path: .extern + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.FILECOIN_FFI_COMMIT }}-${{ env.SOLANA_FFI_COMMIT }} + + # Remove apt repos that are known to break from time to time + # See https://github.com/actions/virtual-environments/issues/323 + - name: Install dependency packages + run: | + for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done + sudo apt-get update + sudo apt-get install -y build-essential + sudo apt-get install -y jq mesa-opencl-icd ocl-icd-opencl-dev pkg-config libudev-dev hwloc libhwloc-dev + curl https://sh.rustup.rs -sSf | sh -s -- -y + source $HOME/.cargo/env + + - name: Get dependencies + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + go get -u github.com/onsi/ginkgo/ginkgo + go get -u github.com/onsi/gomega/... + go get -u golang.org/x/lint/golint + go get -u github.com/loongy/covermerge + go get -u github.com/mattn/goveralls + go get -u github.com/xlab/c-for-go + + - name: Install dependencies (Filecoin FFI) + if: steps.cache-extern.outputs.cache-hit != 'true' + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + cd $GITHUB_WORKSPACE + mkdir .extern && cd .extern + git clone https://github.com/filecoin-project/filecoin-ffi.git + cd filecoin-ffi + git checkout ${{ env.FILECOIN_FFI_COMMIT }} + make + + - name: Install dependencies (Solana FFI) + if: steps.cache-extern.outputs.cache-hit != 'true' + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + cd $GITHUB_WORKSPACE/.extern + git clone https://github.com/renproject/solana-ffi.git + cd solana-ffi + git checkout ${{ env.SOLANA_FFI_COMMIT }} + make clean + make + + - name: Run vetting + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + go mod edit -replace=github.com/filecoin-project/filecoin-ffi=./.extern/filecoin-ffi + go mod edit -replace=github.com/renproject/solana-ffi=./.extern/solana-ffi + go vet ./... + + - name: Run linting + run: | + cd $GITHUB_WORKSPACE + export PATH=$PATH:$(go env GOPATH)/bin + go get -u golang.org/x/lint/golint + golint $(go list ./... | grep -v filecoin-ffi) + + - name: Run multichain infrastructure + run: | + cd $GITHUB_WORKSPACE/infra + source .env + docker-compose up -d --build avalanche + + - name: Sleep until the nodes are up + uses: jakejarvis/wait-action@master + with: + time: '1m' + + - name: Check on docker containers + run: docker ps -a + + - name: Run tests and report test coverage + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source ./infra/.env + cd $GITHUB_WORKSPACE + go test \ + -avax=true \ + -timeout 1500s + test-ftm: + runs-on: ubuntu-latest + env: + FILECOIN_FFI_COMMIT: 8b97bd8230b77bd32f4f27e4766a6d8a03b4e801 + SOLANA_FFI_COMMIT: 1f85d47b5331a2146834bbd28a51654134aedd7d + steps: + - name: Set up Go 1.13 + uses: actions/setup-go@v1 + with: + go-version: 1.13 + id: go + + - name: Configure git for Private Modules + env: + TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + run: git config --global url."https://roynalnaruto:${TOKEN}@github.com".insteadOf "https://github.com" + + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + with: + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + submodules: recursive + + - name: Caching modules + uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-aw-${{ hashFiles('**/go.sum') }} + + - name: Cache extern dependencies (FFI) + id: cache-extern + uses: actions/cache@v2 + env: + cache-name: cache-externs + with: + path: .extern + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.FILECOIN_FFI_COMMIT }}-${{ env.SOLANA_FFI_COMMIT }} + + # Remove apt repos that are known to break from time to time + # See https://github.com/actions/virtual-environments/issues/323 + - name: Install dependency packages + run: | + for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done + sudo apt-get update + sudo apt-get install -y build-essential + sudo apt-get install -y jq mesa-opencl-icd ocl-icd-opencl-dev pkg-config libudev-dev hwloc libhwloc-dev + curl https://sh.rustup.rs -sSf | sh -s -- -y + source $HOME/.cargo/env + + - name: Get dependencies + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + go get -u github.com/onsi/ginkgo/ginkgo + go get -u github.com/onsi/gomega/... + go get -u golang.org/x/lint/golint + go get -u github.com/loongy/covermerge + go get -u github.com/mattn/goveralls + go get -u github.com/xlab/c-for-go + + - name: Install dependencies (Filecoin FFI) + if: steps.cache-extern.outputs.cache-hit != 'true' + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + cd $GITHUB_WORKSPACE + mkdir .extern && cd .extern + git clone https://github.com/filecoin-project/filecoin-ffi.git + cd filecoin-ffi + git checkout ${{ env.FILECOIN_FFI_COMMIT }} + make + + - name: Install dependencies (Solana FFI) + if: steps.cache-extern.outputs.cache-hit != 'true' + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + cd $GITHUB_WORKSPACE/.extern + git clone https://github.com/renproject/solana-ffi.git + cd solana-ffi + git checkout ${{ env.SOLANA_FFI_COMMIT }} + make clean + make + + - name: Run vetting + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + go mod edit -replace=github.com/filecoin-project/filecoin-ffi=./.extern/filecoin-ffi + go mod edit -replace=github.com/renproject/solana-ffi=./.extern/solana-ffi + go vet ./... + - name: Run linting + run: | + cd $GITHUB_WORKSPACE + export PATH=$PATH:$(go env GOPATH)/bin + go get -u golang.org/x/lint/golint + golint $(go list ./... | grep -v filecoin-ffi) + + - name: Run multichain infrastructure + run: | + cd $GITHUB_WORKSPACE/infra + source .env + docker-compose up -d --build fantom + + - name: Sleep until the nodes are up + uses: jakejarvis/wait-action@master + with: + time: '1m' + + - name: Check on docker containers + run: docker ps -a + + - name: Run tests and report test coverage + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source ./infra/.env + cd $GITHUB_WORKSPACE + go test \ + -ftm=true \ + -timeout 1500s + test-polygon: + runs-on: ubuntu-latest + env: + FILECOIN_FFI_COMMIT: 8b97bd8230b77bd32f4f27e4766a6d8a03b4e801 + SOLANA_FFI_COMMIT: 1f85d47b5331a2146834bbd28a51654134aedd7d + steps: + - name: Set up Go 1.13 + uses: actions/setup-go@v1 + with: + go-version: 1.13 + id: go + + - name: Configure git for Private Modules + env: + TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + run: git config --global url."https://roynalnaruto:${TOKEN}@github.com".insteadOf "https://github.com" + + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + with: + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + submodules: recursive + + - name: Caching modules + uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-aw-${{ hashFiles('**/go.sum') }} + + - name: Cache extern dependencies (FFI) + id: cache-extern + uses: actions/cache@v2 + env: + cache-name: cache-externs + with: + path: .extern + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.FILECOIN_FFI_COMMIT }}-${{ env.SOLANA_FFI_COMMIT }} + + # Remove apt repos that are known to break from time to time + # See https://github.com/actions/virtual-environments/issues/323 + - name: Install dependency packages + run: | + for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done + sudo apt-get update + sudo apt-get install -y build-essential + sudo apt-get install -y jq mesa-opencl-icd ocl-icd-opencl-dev pkg-config libudev-dev hwloc libhwloc-dev + curl https://sh.rustup.rs -sSf | sh -s -- -y + source $HOME/.cargo/env + + - name: Get dependencies + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + go get -u github.com/onsi/ginkgo/ginkgo + go get -u github.com/onsi/gomega/... + go get -u golang.org/x/lint/golint + go get -u github.com/loongy/covermerge + go get -u github.com/mattn/goveralls + go get -u github.com/xlab/c-for-go + + - name: Install dependencies (Filecoin FFI) + if: steps.cache-extern.outputs.cache-hit != 'true' + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + cd $GITHUB_WORKSPACE + mkdir .extern && cd .extern + git clone https://github.com/filecoin-project/filecoin-ffi.git + cd filecoin-ffi + git checkout ${{ env.FILECOIN_FFI_COMMIT }} + make + + - name: Install dependencies (Solana FFI) + if: steps.cache-extern.outputs.cache-hit != 'true' + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + cd $GITHUB_WORKSPACE/.extern + git clone https://github.com/renproject/solana-ffi.git + cd solana-ffi + git checkout ${{ env.SOLANA_FFI_COMMIT }} + make clean + make + + - name: Run vetting + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + go mod edit -replace=github.com/filecoin-project/filecoin-ffi=./.extern/filecoin-ffi + go mod edit -replace=github.com/renproject/solana-ffi=./.extern/solana-ffi + go vet ./... + + - name: Run linting + run: | + cd $GITHUB_WORKSPACE + export PATH=$PATH:$(go env GOPATH)/bin + go get -u golang.org/x/lint/golint + golint $(go list ./... | grep -v filecoin-ffi) + + - name: Run multichain infrastructure + run: | + cd $GITHUB_WORKSPACE/infra + source .env + docker-compose up -d --build polygon + + - name: Sleep until the nodes are up + uses: jakejarvis/wait-action@master + with: + time: '1m' + + - name: Check on docker containers + run: docker ps -a + + - name: Run tests and report test coverage + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source ./infra/.env + cd $GITHUB_WORKSPACE + go test \ + -matic=true \ + -timeout 1500s + test-bsc: + runs-on: ubuntu-latest + env: + FILECOIN_FFI_COMMIT: 8b97bd8230b77bd32f4f27e4766a6d8a03b4e801 + SOLANA_FFI_COMMIT: 1f85d47b5331a2146834bbd28a51654134aedd7d + steps: + - name: Set up Go 1.13 + uses: actions/setup-go@v1 + with: + go-version: 1.13 + id: go + + - name: Configure git for Private Modules + env: + TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + run: git config --global url."https://roynalnaruto:${TOKEN}@github.com".insteadOf "https://github.com" + + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + with: + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + submodules: recursive + + - name: Caching modules + uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-aw-${{ hashFiles('**/go.sum') }} + + - name: Cache extern dependencies (FFI) + id: cache-extern + uses: actions/cache@v2 + env: + cache-name: cache-externs + with: + path: .extern + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.FILECOIN_FFI_COMMIT }}-${{ env.SOLANA_FFI_COMMIT }} + + # Remove apt repos that are known to break from time to time + # See https://github.com/actions/virtual-environments/issues/323 + - name: Install dependency packages + run: | + for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done + sudo apt-get update + sudo apt-get install -y build-essential + sudo apt-get install -y jq mesa-opencl-icd ocl-icd-opencl-dev pkg-config libudev-dev hwloc libhwloc-dev + curl https://sh.rustup.rs -sSf | sh -s -- -y + source $HOME/.cargo/env + + - name: Get dependencies + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + go get -u github.com/onsi/ginkgo/ginkgo + go get -u github.com/onsi/gomega/... + go get -u golang.org/x/lint/golint + go get -u github.com/loongy/covermerge + go get -u github.com/mattn/goveralls + go get -u github.com/xlab/c-for-go + + - name: Install dependencies (Filecoin FFI) + if: steps.cache-extern.outputs.cache-hit != 'true' + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + cd $GITHUB_WORKSPACE + mkdir .extern && cd .extern + git clone https://github.com/filecoin-project/filecoin-ffi.git + cd filecoin-ffi + git checkout ${{ env.FILECOIN_FFI_COMMIT }} + make + + - name: Install dependencies (Solana FFI) + if: steps.cache-extern.outputs.cache-hit != 'true' + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + cd $GITHUB_WORKSPACE/.extern + git clone https://github.com/renproject/solana-ffi.git + cd solana-ffi + git checkout ${{ env.SOLANA_FFI_COMMIT }} + make clean + make + + - name: Run vetting + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source $HOME/.cargo/env + go mod edit -replace=github.com/filecoin-project/filecoin-ffi=./.extern/filecoin-ffi + go mod edit -replace=github.com/renproject/solana-ffi=./.extern/solana-ffi + go vet ./... + + - name: Run linting + run: | + cd $GITHUB_WORKSPACE + export PATH=$PATH:$(go env GOPATH)/bin + go get -u golang.org/x/lint/golint + golint $(go list ./... | grep -v filecoin-ffi) + + - name: Run multichain infrastructure + run: | + cd $GITHUB_WORKSPACE/infra + source .env + docker-compose up -d --build binance + + - name: Sleep until the nodes are up + uses: jakejarvis/wait-action@master + with: + time: '1m' + + - name: Check on docker containers + run: docker ps -a + + - name: Run tests and report test coverage + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + export PATH=$PATH:$(go env GOPATH)/bin + source ./infra/.env + cd $GITHUB_WORKSPACE + go test \ + -bsc=true \ + -timeout 1500s test-eth: runs-on: ubuntu-latest env: diff --git a/chain/avalanche/address.go b/chain/avalanche/address.go new file mode 100644 index 00000000..62a1c56a --- /dev/null +++ b/chain/avalanche/address.go @@ -0,0 +1,33 @@ +package avalanche + +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/avalanche/address_test.go b/chain/avalanche/address_test.go new file mode 100644 index 00000000..65891a7f --- /dev/null +++ b/chain/avalanche/address_test.go @@ -0,0 +1,105 @@ +package avalanche_test + +import ( + "encoding/hex" + "encoding/json" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/renproject/multichain/chain/avalanche" + "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 := avalanche.Address(x) + Expect(addr.SizeHint()).To(Equal(20)) + + bytes, err := surge.ToBinary(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr avalanche.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 := avalanche.Address(x) + + bytes, err := json.Marshal(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr avalanche.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 avalanche.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 avalanche.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 avalanche.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/avalanche/client.go b/chain/avalanche/client.go new file mode 100644 index 00000000..914c61bc --- /dev/null +++ b/chain/avalanche/client.go @@ -0,0 +1,17 @@ +package avalanche + +import ( + "github.com/renproject/multichain/chain/ethereum" +) + +const ( + // DefaultClientRPCURL is the RPC URL used by default, to interact with the + // avalanche node. + DefaultClientRPCURL = "http://127.0.0.1:9650/ext/bc/C/rpc" +) + +// Client re-exports ethereum.Client. +type Client = ethereum.Client + +// NewClient re-exports ethereum.NewClient. +var NewClient = ethereum.NewClient diff --git a/chain/avalanche/encode.go b/chain/avalanche/encode.go new file mode 100644 index 00000000..4c3a01e5 --- /dev/null +++ b/chain/avalanche/encode.go @@ -0,0 +1,11 @@ +package avalanche + +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/avalanche/encode_test.go b/chain/avalanche/encode_test.go new file mode 100644 index 00000000..2bfd841c --- /dev/null +++ b/chain/avalanche/encode_test.go @@ -0,0 +1,255 @@ +package avalanche_test + +import ( + "encoding/hex" + "fmt" + "github.com/renproject/multichain/chain/avalanche" + "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 := avalanche.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 := avalanche.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 := avalanche.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 := avalanche.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 := avalanche.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 := avalanche.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 := avalanche.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 := avalanche.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 := avalanche.Address(x) + + resBytes := avalanche.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() { avalanche.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 avalanche.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 := avalanche.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/avalanche/gas.go b/chain/avalanche/gas.go new file mode 100644 index 00000000..aa3431c9 --- /dev/null +++ b/chain/avalanche/gas.go @@ -0,0 +1,11 @@ +package avalanche + +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/avalanche/tx.go b/chain/avalanche/tx.go new file mode 100644 index 00000000..0a68d414 --- /dev/null +++ b/chain/avalanche/tx.go @@ -0,0 +1,16 @@ +package avalanche + +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 diff --git a/chain/bsc/address.go b/chain/bsc/address.go new file mode 100644 index 00000000..ad134de1 --- /dev/null +++ b/chain/bsc/address.go @@ -0,0 +1,33 @@ +package bsc + +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/bsc/address_test.go b/chain/bsc/address_test.go new file mode 100644 index 00000000..8954ec30 --- /dev/null +++ b/chain/bsc/address_test.go @@ -0,0 +1,105 @@ +package bsc_test + +import ( + "encoding/hex" + "encoding/json" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/renproject/multichain/chain/bsc" + "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 := bsc.Address(x) + Expect(addr.SizeHint()).To(Equal(20)) + + bytes, err := surge.ToBinary(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr bsc.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 := bsc.Address(x) + + bytes, err := json.Marshal(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr bsc.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 bsc.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 bsc.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 bsc.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/bsc/client.go b/chain/bsc/client.go new file mode 100644 index 00000000..4bf177cd --- /dev/null +++ b/chain/bsc/client.go @@ -0,0 +1,17 @@ +package bsc + +import ( + "github.com/renproject/multichain/chain/ethereum" +) + +const ( + // DefaultClientRPCURL is the RPC URL used by default, to interact with the + // bsc node. + DefaultClientRPCURL = "http://127.0.0.1:8575/" +) + +// Client re-exports ethereum.Client. +type Client = ethereum.Client + +// NewClient re-exports ethereum.NewClient. +var NewClient = ethereum.NewClient diff --git a/chain/bsc/encode.go b/chain/bsc/encode.go new file mode 100644 index 00000000..1c44872d --- /dev/null +++ b/chain/bsc/encode.go @@ -0,0 +1,11 @@ +package bsc + +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/bsc/encode_test.go b/chain/bsc/encode_test.go new file mode 100644 index 00000000..362ee0df --- /dev/null +++ b/chain/bsc/encode_test.go @@ -0,0 +1,255 @@ +package bsc_test + +import ( + "encoding/hex" + "fmt" + "github.com/renproject/multichain/chain/bsc" + "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 := bsc.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 := bsc.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 := bsc.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 := bsc.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 := bsc.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 := bsc.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 := bsc.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 := bsc.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 := bsc.Address(x) + + resBytes := bsc.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() { bsc.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 bsc.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 := bsc.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/bsc/gas.go b/chain/bsc/gas.go new file mode 100644 index 00000000..e177a1e1 --- /dev/null +++ b/chain/bsc/gas.go @@ -0,0 +1,11 @@ +package bsc + +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/bsc/tx.go b/chain/bsc/tx.go new file mode 100644 index 00000000..a91a9eea --- /dev/null +++ b/chain/bsc/tx.go @@ -0,0 +1,16 @@ +package bsc + +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 diff --git a/chain/fantom/address.go b/chain/fantom/address.go new file mode 100644 index 00000000..1a81be2d --- /dev/null +++ b/chain/fantom/address.go @@ -0,0 +1,33 @@ +package fantom + +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/fantom/address_test.go b/chain/fantom/address_test.go new file mode 100644 index 00000000..2a9c500e --- /dev/null +++ b/chain/fantom/address_test.go @@ -0,0 +1,105 @@ +package fantom_test + +import ( + "encoding/hex" + "encoding/json" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/renproject/multichain/chain/fantom" + "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 := fantom.Address(x) + Expect(addr.SizeHint()).To(Equal(20)) + + bytes, err := surge.ToBinary(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr fantom.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 := fantom.Address(x) + + bytes, err := json.Marshal(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr fantom.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 fantom.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 fantom.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 fantom.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/fantom/client.go b/chain/fantom/client.go new file mode 100644 index 00000000..92504aa3 --- /dev/null +++ b/chain/fantom/client.go @@ -0,0 +1,17 @@ +package fantom + +import ( + "github.com/renproject/multichain/chain/ethereum" +) + +const ( + // DefaultClientRPCURL is the RPC URL used by default, to interact with the + // fantom node. + DefaultClientRPCURL = "http://127.0.0.1:18545/" +) + +// Client re-exports ethereum.Client. +type Client = ethereum.Client + +// NewClient re-exports ethereum.NewClient. +var NewClient = ethereum.NewClient diff --git a/chain/fantom/encode.go b/chain/fantom/encode.go new file mode 100644 index 00000000..8e07626d --- /dev/null +++ b/chain/fantom/encode.go @@ -0,0 +1,11 @@ +package fantom + +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/fantom/encode_test.go b/chain/fantom/encode_test.go new file mode 100644 index 00000000..7c2c4fda --- /dev/null +++ b/chain/fantom/encode_test.go @@ -0,0 +1,255 @@ +package fantom_test + +import ( + "encoding/hex" + "fmt" + "github.com/renproject/multichain/chain/fantom" + "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 := fantom.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 := fantom.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 := fantom.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 := fantom.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 := fantom.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 := fantom.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 := fantom.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 := fantom.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 := fantom.Address(x) + + resBytes := fantom.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() { fantom.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 fantom.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 := fantom.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/fantom/gas.go b/chain/fantom/gas.go new file mode 100644 index 00000000..68a8a100 --- /dev/null +++ b/chain/fantom/gas.go @@ -0,0 +1,11 @@ +package fantom + +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/fantom/tx.go b/chain/fantom/tx.go new file mode 100644 index 00000000..c4244288 --- /dev/null +++ b/chain/fantom/tx.go @@ -0,0 +1,16 @@ +package fantom + +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 diff --git a/chain/polygon/address.go b/chain/polygon/address.go new file mode 100644 index 00000000..fecb03c3 --- /dev/null +++ b/chain/polygon/address.go @@ -0,0 +1,33 @@ +package polygon + +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/polygon/address_test.go b/chain/polygon/address_test.go new file mode 100644 index 00000000..760a5197 --- /dev/null +++ b/chain/polygon/address_test.go @@ -0,0 +1,105 @@ +package polygon_test + +import ( + "encoding/hex" + "encoding/json" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/renproject/multichain/chain/polygon" + "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 := polygon.Address(x) + Expect(addr.SizeHint()).To(Equal(20)) + + bytes, err := surge.ToBinary(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr polygon.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 := polygon.Address(x) + + bytes, err := json.Marshal(addr) + Expect(err).ToNot(HaveOccurred()) + + var newAddr polygon.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 polygon.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 polygon.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 polygon.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/polygon/client.go b/chain/polygon/client.go new file mode 100644 index 00000000..03c599fa --- /dev/null +++ b/chain/polygon/client.go @@ -0,0 +1,17 @@ +package polygon + +import ( + "github.com/renproject/multichain/chain/ethereum" +) + +const ( + // DefaultClientRPCURL is the RPC URL used by default, to interact with the + // polygon node. + DefaultClientRPCURL = "http://127.0.0.1:28545/" +) + +// Client re-exports ethereum.Client. +type Client = ethereum.Client + +// NewClient re-exports ethereum.NewClient. +var NewClient = ethereum.NewClient diff --git a/chain/polygon/encode.go b/chain/polygon/encode.go new file mode 100644 index 00000000..4472faa1 --- /dev/null +++ b/chain/polygon/encode.go @@ -0,0 +1,11 @@ +package polygon + +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/polygon/encode_test.go b/chain/polygon/encode_test.go new file mode 100644 index 00000000..95e49d93 --- /dev/null +++ b/chain/polygon/encode_test.go @@ -0,0 +1,255 @@ +package polygon_test + +import ( + "encoding/hex" + "fmt" + "github.com/renproject/multichain/chain/polygon" + "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 := polygon.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 := polygon.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 := polygon.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 := polygon.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 := polygon.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 := polygon.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 := polygon.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 := polygon.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 := polygon.Address(x) + + resBytes := polygon.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() { polygon.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 polygon.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 := polygon.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/polygon/gas.go b/chain/polygon/gas.go new file mode 100644 index 00000000..c1a71020 --- /dev/null +++ b/chain/polygon/gas.go @@ -0,0 +1,11 @@ +package polygon + +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/polygon/tx.go b/chain/polygon/tx.go new file mode 100644 index 00000000..a54f219b --- /dev/null +++ b/chain/polygon/tx.go @@ -0,0 +1,16 @@ +package polygon + +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 diff --git a/infra/avalanche/run.sh b/infra/avalanche/run.sh index 925c57c3..92026a56 100644 --- a/infra/avalanche/run.sh +++ b/infra/avalanche/run.sh @@ -35,6 +35,9 @@ avalanchego \ # create a new user sleep 10 +echo "" +echo "---create new user---" +echo "" curl -X POST --data '{ "jsonrpc":"2.0", "id" :1, @@ -47,6 +50,9 @@ curl -X POST --data '{ # import private key that contains AVAX into X-chain sleep 1 +echo "" +echo "---import private key that contains AVAX into X-chain---" +echo "" curl -X POST --data '{ "jsonrpc":"2.0", "id" :1, @@ -60,6 +66,9 @@ curl -X POST --data '{ # import private key into C-chain sleep 1 +echo "" +echo "---import private key into C-chain---" +echo "" curl -X POST --data '{ "jsonrpc":"2.0", "id" :1, @@ -73,6 +82,9 @@ curl -X POST --data '{ # export the AVAX to C-chain sleep 1 +echo "" +echo "---export the AVAX to C-chain---" +echo "" curl -X POST --data '{ "jsonrpc":"2.0", "id" :1, @@ -88,6 +100,9 @@ curl -X POST --data '{ # import AVAX to the hex address sleep 1 +echo "" +echo "---import AVAX to the hex address---" +echo "" curl -X POST --data '{ "jsonrpc":"2.0", "id" :1, diff --git a/infra/binance/run.sh b/infra/binance/run.sh index 6355dadf..d3b97641 100644 --- a/infra/binance/run.sh +++ b/infra/binance/run.sh @@ -9,4 +9,6 @@ ganache-cli \ -i 420 \ -m "$MNEMONIC" \ -p 8575 \ - -u $ADDRESS + -u $ADDRESS \ + -b 10 \ + --chainId 1337 diff --git a/multichain_test.go b/multichain_test.go index ad08000f..57799b92 100644 --- a/multichain_test.go +++ b/multichain_test.go @@ -8,13 +8,20 @@ import ( "flag" "fmt" "github.com/btcsuite/btcutil/hdkeychain" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/crypto" + "github.com/renproject/multichain/chain/avalanche" + "github.com/renproject/multichain/chain/bsc" "github.com/renproject/multichain/chain/ethereum" + "github.com/renproject/multichain/chain/fantom" + "github.com/renproject/multichain/chain/polygon" "github.com/tyler-smith/go-bip39" + "io/ioutil" "math/big" "math/rand" "os" "os/exec" + "path/filepath" "reflect" "strings" "testing/quick" @@ -49,13 +56,17 @@ import ( ) var ( - testBTC = flag.Bool("btc", false, "Pass this flag to test Bitcoin") - testBCH = flag.Bool("bch", false, "Pass this flag to test Bitcoincash") - testDOGE = flag.Bool("doge", false, "Pass this flag to test Dogecoin") - testFIL = flag.Bool("fil", false, "Pass this flag to test Filecoin") - testETH = flag.Bool("eth", false, "Pass this flag to test Ethereum") - testLUNA = flag.Bool("luna", false, "Pass this flag to test Terra") - testZEC = flag.Bool("zec", false, "Pass this flag to test Zcash") + testBTC = flag.Bool("btc", false, "Pass this flag to test Bitcoin") + testBCH = flag.Bool("bch", false, "Pass this flag to test Bitcoincash") + testDOGE = flag.Bool("doge", false, "Pass this flag to test Dogecoin") + testFIL = flag.Bool("fil", false, "Pass this flag to test Filecoin") + testETH = flag.Bool("eth", false, "Pass this flag to test Ethereum") + testMATIC = flag.Bool("matic", false, "Pass this flag to test Polygon") + testAVAX = flag.Bool("avax", false, "Pass this flag to test Avalanche") + testBSC = flag.Bool("bsc", false, "Pass this flag to test Binance Smart Chain") + testFTM = flag.Bool("ftm", false, "Pass this flag to test Fantom") + testLUNA = flag.Bool("luna", false, "Pass this flag to test Terra") + testZEC = flag.Bool("zec", false, "Pass this flag to test Zcash") ) var _ = Describe("Multichain", func() { @@ -78,6 +89,10 @@ var _ = Describe("Multichain", func() { testFlags[multichain.Dogecoin] = *testDOGE testFlags[multichain.Filecoin] = *testFIL testFlags[multichain.Ethereum] = *testETH + testFlags[multichain.BinanceSmartChain] = *testBSC + testFlags[multichain.Polygon] = *testMATIC + testFlags[multichain.Avalanche] = *testAVAX + testFlags[multichain.Fantom] = *testFTM testFlags[multichain.Terra] = *testLUNA testFlags[multichain.Zcash] = *testZEC @@ -102,6 +117,14 @@ var _ = Describe("Multichain", func() { multichain.Filecoin, multichain.FIL, }, + { + multichain.Ethereum, + multichain.ETH, + }, + { + multichain.BinanceSmartChain, + multichain.BNB, + }, { multichain.Moonbeam, multichain.GLMR, @@ -499,7 +522,7 @@ var _ = Describe("Multichain", func() { func(privKey id.PrivKey) multichain.Address { return multichain.Address(crypto.PubkeyToAddress(privKey.PublicKey).Hex()) }, - "http://127.0.0.1:8545", + ethereum.DefaultClientRPCURL, func() multichain.Address { recipientKey := id.NewPrivKey() return multichain.Address(crypto.PubkeyToAddress(recipientKey.PublicKey).Hex()) @@ -521,6 +544,172 @@ var _ = Describe("Multichain", func() { }, multichain.Ethereum, }, + { + func() (id.PrivKey, *id.PubKey, multichain.Address) { + keyPath := filepath.Join(".", "infra", "polygon", "json-keystore") + keyjson, err := ioutil.ReadFile(fmt.Sprintf("%v", keyPath)) + Expect(err).NotTo(HaveOccurred()) + password := "password0" + keyStoreKey, err := keystore.DecryptKey(keyjson, password) + Expect(err).NotTo(HaveOccurred()) + newKey := keyStoreKey.PrivateKey + pk := (*id.PrivKey)(newKey) + address := multichain.Address(crypto.PubkeyToAddress(pk.PublicKey).Hex()) + return *pk, pk.PubKey(), address + }, + func(privKey id.PrivKey) multichain.Address { + return multichain.Address(crypto.PubkeyToAddress(privKey.PublicKey).Hex()) + }, + polygon.DefaultClientRPCURL, + func() multichain.Address { + recipientKey := id.NewPrivKey() + return multichain.Address(crypto.PubkeyToAddress(recipientKey.PublicKey).Hex()) + }, + func(rpcURL pack.String) (multichain.AccountClient, multichain.AccountTxBuilder) { + client, err := polygon.NewClient(string(rpcURL)) + Expect(err).NotTo(HaveOccurred()) + txBuilder := polygon.NewTxBuilder(big.NewInt(15001)) + + return client, txBuilder + }, + func(_ multichain.AccountClient) (pack.U256, pack.U256, pack.U256, pack.U256, pack.Bytes) { + amount := pack.NewU256FromU64(pack.U64(2000000)) + gasLimit := pack.NewU256FromU64(pack.U64(1000000)) + gasPrice := pack.NewU256FromU64(pack.U64(1000000000000)) + gasCap := pack.NewU256FromInt(gasPrice.Int()) + payload := pack.NewBytes([]byte("multichain")) + return amount, gasLimit, gasPrice, gasCap, payload + }, + multichain.Polygon, + }, + { + func() (id.PrivKey, *id.PubKey, multichain.Address) { + mnemonic := os.Getenv("BINANCE_MNEMONIC") + if mnemonic == "" { + panic("BINANCE_MNEMONIC is undefined") + } + const ZERO uint32 = 0x80000000 + path := []uint32{ZERO + 44, ZERO + 60, ZERO, 0, 0} + path[len(path)-1] = uint32(0) + seed := bip39.NewSeed(mnemonic, "") + key, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) + Expect(err).NotTo(HaveOccurred()) + for _, val := range path { + key, err = key.Child(val) + if err != nil { + Expect(err).NotTo(HaveOccurred()) + } + } + privKey, err := key.ECPrivKey() + if err != nil { + Expect(err).NotTo(HaveOccurred()) + } + + newKey := privKey.ToECDSA() + Expect(err).NotTo(HaveOccurred()) + pk := (*id.PrivKey)(newKey) + address := multichain.Address(crypto.PubkeyToAddress(pk.PublicKey).Hex()) + return *pk, pk.PubKey(), address + }, + func(privKey id.PrivKey) multichain.Address { + return multichain.Address(crypto.PubkeyToAddress(privKey.PublicKey).Hex()) + }, + bsc.DefaultClientRPCURL, + func() multichain.Address { + recipientKey := id.NewPrivKey() + return multichain.Address(crypto.PubkeyToAddress(recipientKey.PublicKey).Hex()) + }, + func(rpcURL pack.String) (multichain.AccountClient, multichain.AccountTxBuilder) { + client, err := bsc.NewClient(string(rpcURL)) + Expect(err).NotTo(HaveOccurred()) + txBuilder := bsc.NewTxBuilder(big.NewInt(1337)) + + return client, txBuilder + }, + func(_ multichain.AccountClient) (pack.U256, pack.U256, pack.U256, pack.U256, pack.Bytes) { + amount := pack.NewU256FromU64(pack.U64(2000000)) + gasLimit := pack.NewU256FromU64(pack.U64(100000)) + gasPrice := pack.NewU256FromU64(pack.U64(1)) + gasCap := pack.NewU256FromInt(gasPrice.Int()) + payload := pack.NewBytes([]byte("multichain")) + return amount, gasLimit, gasPrice, gasCap, payload + }, + multichain.BinanceSmartChain, + }, + { + func() (id.PrivKey, *id.PubKey, multichain.Address) { + pk := os.Getenv("C_AVAX_PK") + if pk == "" { + panic("C_AVAX_PK is undefined") + } + pk = strings.TrimPrefix(pk, "0x") + key, err := crypto.HexToECDSA(pk) + privKey := (*id.PrivKey)(key) + Expect(err).NotTo(HaveOccurred()) + address := multichain.Address(crypto.PubkeyToAddress(privKey.PublicKey).Hex()) + return *privKey, privKey.PubKey(), address + }, + func(privKey id.PrivKey) multichain.Address { + return multichain.Address(crypto.PubkeyToAddress(privKey.PublicKey).Hex()) + }, + avalanche.DefaultClientRPCURL, + func() multichain.Address { + recipientKey := id.NewPrivKey() + return multichain.Address(crypto.PubkeyToAddress(recipientKey.PublicKey).Hex()) + }, + func(rpcURL pack.String) (multichain.AccountClient, multichain.AccountTxBuilder) { + client, err := avalanche.NewClient(string(rpcURL)) + Expect(err).NotTo(HaveOccurred()) + txBuilder := avalanche.NewTxBuilder(big.NewInt(43112)) + return client, txBuilder + }, + func(_ multichain.AccountClient) (pack.U256, pack.U256, pack.U256, pack.U256, pack.Bytes) { + amount := pack.NewU256FromU64(pack.U64(1)) + gasLimit := pack.NewU256FromU64(pack.U64(100000)) + gasPrice := pack.NewU256FromU64(pack.U64(225000000000)) + gasCap := pack.NewU256FromInt(gasPrice.Int()) + payload := pack.NewBytes([]byte("")) + return amount, gasLimit, gasPrice, gasCap, payload + }, + multichain.Avalanche, + }, + { + func() (id.PrivKey, *id.PubKey, multichain.Address) { + pk := os.Getenv("FANTOM_PK") + if pk == "" { + panic("FANTOM_PK is undefined") + } + key, err := crypto.HexToECDSA(pk) + privKey := (*id.PrivKey)(key) + Expect(err).NotTo(HaveOccurred()) + address := multichain.Address(crypto.PubkeyToAddress(privKey.PublicKey).Hex()) + return *privKey, privKey.PubKey(), address + }, + func(privKey id.PrivKey) multichain.Address { + return multichain.Address(crypto.PubkeyToAddress(privKey.PublicKey).Hex()) + }, + fantom.DefaultClientRPCURL, + func() multichain.Address { + recipientKey := id.NewPrivKey() + return multichain.Address(crypto.PubkeyToAddress(recipientKey.PublicKey).Hex()) + }, + func(rpcURL pack.String) (multichain.AccountClient, multichain.AccountTxBuilder) { + client, err := fantom.NewClient(string(rpcURL)) + Expect(err).NotTo(HaveOccurred()) + txBuilder := fantom.NewTxBuilder(big.NewInt(4003)) + + return client, txBuilder + }, + func(_ multichain.AccountClient) (pack.U256, pack.U256, pack.U256, pack.U256, pack.Bytes) { + amount := pack.NewU256FromU64(pack.U64(2000000)) + gasLimit := pack.NewU256FromU64(pack.U64(1000000)) + gasPrice := pack.NewU256FromU64(pack.U64(1000000000)) + gasCap := pack.NewU256FromInt(gasPrice.Int()) + payload := pack.NewBytes([]byte("multichain")) + return amount, gasLimit, gasPrice, gasCap, payload + }, + multichain.Fantom, + }, { func() (id.PrivKey, *id.PubKey, multichain.Address) { pkEnv := os.Getenv("TERRA_PK") @@ -674,47 +863,53 @@ var _ = Describe("Multichain", func() { // Initialise the account chain's client, and possibly get a nonce for // the sender. accountClient, txBuilder := accountChain.initialise(accountChain.rpcURL) + sendTx := func() (pack.Bytes, pack.U256) { + // Get the appropriate nonce for sender. + nonce, err := accountClient.AccountNonce(ctx, senderAddr) + Expect(err).NotTo(HaveOccurred()) - // Get the appropriate nonce for sender. - nonce, err := accountClient.AccountNonce(ctx, senderAddr) - Expect(err).NotTo(HaveOccurred()) - - // Build a transaction. - amount, gasLimit, gasPrice, gasCap, payload := accountChain.txParams(accountClient) + // Build a transaction. + amount, gasLimit, gasPrice, gasCap, payload := accountChain.txParams(accountClient) - accountTx, err := txBuilder.BuildTx( - ctx, - multichain.Address(senderAddr), - recipientAddr, - amount, nonce, gasLimit, gasPrice, gasCap, - payload, - ) - Expect(err).NotTo(HaveOccurred()) - - // Get the transaction bytes and sign them. - sighashes, err := accountTx.Sighashes() - Expect(err).NotTo(HaveOccurred()) - hash := id.Hash(sighashes[0]) - sig, err := senderPrivKey.Sign(&hash) - Expect(err).NotTo(HaveOccurred()) - sigBytes, err := surge.ToBinary(sig) - Expect(err).NotTo(HaveOccurred()) - txSignature := pack.Bytes65{} - copy(txSignature[:], sigBytes) - senderPubKeyBytes, err := surge.ToBinary(senderPubKey) - Expect(err).NotTo(HaveOccurred()) - err = accountTx.Sign( - []pack.Bytes65{txSignature}, - pack.NewBytes(senderPubKeyBytes), - ) - Expect(err).NotTo(HaveOccurred()) + accountTx, err := txBuilder.BuildTx( + ctx, + multichain.Address(senderAddr), + recipientAddr, + amount, nonce, gasLimit, gasPrice, gasCap, + payload, + ) + Expect(err).NotTo(HaveOccurred()) - // Submit the transaction to the account chain. - txHash := accountTx.Hash() - err = accountClient.SubmitTx(ctx, accountTx) - Expect(err).NotTo(HaveOccurred()) - logger.Debug("submit tx", zap.String("from", string(senderAddr)), zap.String("to", string(recipientAddr)), zap.Any("txHash", txHash)) + // Get the transaction bytes and sign them. + sighashes, err := accountTx.Sighashes() + Expect(err).NotTo(HaveOccurred()) + hash := id.Hash(sighashes[0]) + sig, err := senderPrivKey.Sign(&hash) + Expect(err).NotTo(HaveOccurred()) + sigBytes, err := surge.ToBinary(sig) + Expect(err).NotTo(HaveOccurred()) + txSignature := pack.Bytes65{} + copy(txSignature[:], sigBytes) + senderPubKeyBytes, err := surge.ToBinary(senderPubKey) + Expect(err).NotTo(HaveOccurred()) + err = accountTx.Sign( + []pack.Bytes65{txSignature}, + pack.NewBytes(senderPubKeyBytes), + ) + Expect(err).NotTo(HaveOccurred()) + // Submit the transaction to the account chain. + txHash := accountTx.Hash() + err = accountClient.SubmitTx(ctx, accountTx) + Expect(err).NotTo(HaveOccurred()) + logger.Debug("submit tx", zap.String("from", string(senderAddr)), zap.String("to", string(recipientAddr)), zap.Any("txHash", txHash)) + return txHash, amount + } + txHash, amount := sendTx() + if accountChain.chain == multichain.Avalanche { + time.Sleep(5 * time.Second) + sendTx() + } // Wait slightly before we query the chain's node. time.Sleep(time.Second)