diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0aa033f5..90b332a5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,7 +68,7 @@ jobs: dogecoin \ terra \ zcash - docker run -d -p 1234:1234 -h 0.0.0.0 \ + docker run -d -p 1234:1234 -h 0.0.0.0 \ --name infra_filecoin_1 rohitnarurkar/multichain_filecoin:latest - name: Sleep until the nodes are up @@ -86,7 +86,7 @@ jobs: export PATH=$PATH:$(go env GOPATH)/bin source ./infra/.env cd $GITHUB_WORKSPACE - CI=true go test -timeout 1500s + go test -timeout 1500s build: runs-on: ubuntu-latest diff --git a/chain/acala/acala.go b/chain/acala/acala.go new file mode 100644 index 00000000..cc85e94d --- /dev/null +++ b/chain/acala/acala.go @@ -0,0 +1,169 @@ +package acala + +import ( + "context" + "fmt" + + gsrpc "github.com/centrifuge/go-substrate-rpc-client" + "github.com/centrifuge/go-substrate-rpc-client/types" + "github.com/renproject/multichain/api/address" + "github.com/renproject/multichain/api/contract" + "github.com/renproject/pack" + "github.com/renproject/surge" + "go.uber.org/zap" +) + +const ( + // DefaultClientRPCURL is the default RPC URL used while connecting to an + // Acala node. + DefaultClientRPCURL = "ws://127.0.0.1:9944" +) + +// ClientOptions define the options for Acala client. +type ClientOptions struct { + Logger *zap.Logger + rpcURL pack.String +} + +// DefaultClientOptions are the Acala client options used by default. +func DefaultClientOptions() ClientOptions { + logger, err := zap.NewDevelopment() + if err != nil { + panic(err) + } + return ClientOptions{ + Logger: logger, + rpcURL: DefaultClientRPCURL, + } +} + +// WithRPCURL sets the RPC URL in the client options to return a new instance +// of client options. +func (opts ClientOptions) WithRPCURL(rpcURL pack.String) ClientOptions { + opts.rpcURL = rpcURL + return opts +} + +// Client represents an Acala client. It interacts with a Substrate node (Acala) +// via the Go-substrate RPC API. +type Client struct { + opts ClientOptions + api gsrpc.SubstrateAPI +} + +// NewClient constructs a new Acala client using the client options. +func NewClient(opts ClientOptions) (*Client, error) { + substrateAPI, err := gsrpc.NewSubstrateAPI(string(opts.rpcURL)) + if err != nil { + return nil, err + } + + return &Client{ + opts: opts, + api: *substrateAPI, + }, nil +} + +// BurnLogInput defines a structure used to serialize relevant data to fetch +// Acala burn logs from RenVmBridge's system events. +type BurnLogInput struct { + Blockhash pack.Bytes32 + ExtSign pack.Bytes +} + +// BurnLogOutput defines a structure used to represent burn data from Acala. +type BurnLogOutput struct { + Amount pack.U256 + Recipient address.RawAddress + Confs pack.U64 +} + +// CallContractSystemEvents fetches burn logs from the substrate system events. +func (client *Client) CallContractSystemEvents(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { + // Deserialise the calldata bytes. + input := BurnLogInput{} + if err := surge.FromBinary(&input, calldata); err != nil { + return pack.Bytes{}, fmt.Errorf("deserialise calldata: %v", err) + } + + // Get chain metadata. + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) + } + + // This key is used to read the state storage at the block of interest. + key, err := types.CreateStorageKey(meta, "System", "Events", nil, nil) + if err != nil { + return pack.Bytes{}, fmt.Errorf("create storage key: %v", err) + } + + // Get the block in which the burn event was logged. + block, err := client.api.RPC.Chain.GetBlock(types.Hash(input.Blockhash)) + if err != nil { + return pack.Bytes{}, fmt.Errorf("get block: %v", err) + } + + // Get the latest block header. This will be used to calculate number of block + // confirmations of the burn log of interest. + header, err := client.api.RPC.Chain.GetHeaderLatest() + if err != nil { + return pack.Bytes{}, fmt.Errorf("get header: %v", err) + } + + // Retrieve raw bytes from storage at the block and storage key of interest. + data, err := client.api.RPC.State.GetStorageRaw(key, types.Hash(input.Blockhash)) + if err != nil { + return pack.Bytes{}, fmt.Errorf("get storage: %v", err) + } + + // Fetch the extrinsic's index in the block. + extID := -1 + for i, ext := range block.Block.Extrinsics { + if input.ExtSign.Equal(pack.Bytes(ext.Signature.Signature.AsSr25519[:])) { + extID = i + break + } + } + if extID == -1 { + return pack.Bytes{}, fmt.Errorf("extrinsic not found in block") + } + + // Decode the event data to get the burn log. + burnEvent, err := decodeEventData(meta, data, uint32(extID)) + if err != nil { + return pack.Bytes{}, err + } + + // Calculate block confirmations for the event. + confs := header.Number - block.Block.Header.Number + 1 + + burnLogOutput := BurnLogOutput{ + Amount: pack.NewU256FromInt(burnEvent.Amount.Int), + Recipient: address.RawAddress(burnEvent.Dest[:]), + Confs: pack.NewU64(uint64(confs)), + } + + out, err := surge.ToBinary(burnLogOutput) + if err != nil { + return pack.Bytes{}, fmt.Errorf("serialise output: %v", err) + } + + return pack.Bytes(out), nil +} + +func decodeEventData(meta *types.Metadata, data *types.StorageDataRaw, id uint32) (eventBurnt, error) { + events := RenVmBridgeEvents{} + if err := types.EventRecordsRaw(*data).DecodeEventRecords(meta, &events); err != nil { + return eventBurnt{}, fmt.Errorf("decode event data: %v", err) + } + + // Match the event to the appropriate extrinsic index. + for _, event := range events.RenVmBridge_Burnt { + if event.Phase.AsApplyExtrinsic == id { + return event, nil + } + } + + return eventBurnt{}, fmt.Errorf("burn event not found") +} diff --git a/chain/acala/acala_suite_test.go b/chain/acala/acala_suite_test.go new file mode 100644 index 00000000..f91d1e63 --- /dev/null +++ b/chain/acala/acala_suite_test.go @@ -0,0 +1,13 @@ +package acala_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestSubstratecompat(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Acala Suite") +} diff --git a/chain/acala/acala_test.go b/chain/acala/acala_test.go new file mode 100644 index 00000000..dc367890 --- /dev/null +++ b/chain/acala/acala_test.go @@ -0,0 +1 @@ +package acala_test diff --git a/chain/acala/address.go b/chain/acala/address.go new file mode 100644 index 00000000..8a478005 --- /dev/null +++ b/chain/acala/address.go @@ -0,0 +1,91 @@ +package acala + +import ( + "fmt" + + "github.com/btcsuite/btcutil/base58" + "github.com/renproject/multichain" + + "golang.org/x/crypto/blake2b" +) + +const ( + // AddressTypeDefault is the default address type byte for a substrate chain. + AddressTypeDefault = byte(42) + // AddressTypeTestnet is the address type used for testnet. + AddressTypeTestnet = byte(42) + // AddressTypeCanaryNetwork is the address type used for canary network. + AddressTypeCanaryNetwork = byte(8) + // AddressTypeMainnet is the address type used for mainnet. + AddressTypeMainnet = byte(10) +) + +var ( + // Prefix used before hashing the address bytes for calculating checksum + Prefix = []byte("SS58PRE") +) + +// GetAddressType returns the appropriate prefix address type for a network +// type. +func GetAddressType(network multichain.Network) byte { + switch network { + case multichain.NetworkLocalnet, multichain.NetworkDevnet: + return AddressTypeDefault + case multichain.NetworkTestnet: + return AddressTypeTestnet + case multichain.NetworkMainnet: + return AddressTypeMainnet + default: + return AddressTypeDefault + } +} + +// AddressDecoder implements the address.Decoder interface. +type AddressDecoder struct { + addressType byte +} + +// AddressEncoder implements the address.Encoder interface. +type AddressEncoder struct { + addressType byte +} + +// AddressEncodeDecoder implements the address.EncodeDecoder interface. +type AddressEncodeDecoder struct { + AddressEncoder + AddressDecoder +} + +// NewAddressEncodeDecoder constructs a new AddressEncodeDecoder. +func NewAddressEncodeDecoder(addressType byte) AddressEncodeDecoder { + return AddressEncodeDecoder{ + AddressEncoder: AddressEncoder{addressType}, + AddressDecoder: AddressDecoder{addressType}, + } +} + +// DecodeAddress the string using the Bitcoin base58 alphabet. The substrate +// address is decoded and only the 32-byte public key is returned as the raw +// address. +func (decoder AddressDecoder) DecodeAddress(addr multichain.Address) (multichain.RawAddress, error) { + data := base58.Decode(string(addr)) + if len(data) != 35 { + return multichain.RawAddress([]byte{}), fmt.Errorf("expected 35 bytes, got %v bytes", len(data)) + } + return multichain.RawAddress(data[1:33]), nil +} + +// EncodeAddress the raw bytes using the Bitcoin base58 alphabet. We expect a +// 32-byte substrate public key as the address in its raw bytes representation. +// A checksum encoded key is then encoded in the base58 format. +func (encoder AddressEncoder) EncodeAddress(rawAddr multichain.RawAddress) (multichain.Address, error) { + if len(rawAddr) != 32 { + return multichain.Address(""), fmt.Errorf("expected 32 bytes, got %v bytes", len(rawAddr)) + } + checksummedAddr := append([]byte{encoder.addressType}, rawAddr...) + checksum := blake2b.Sum512(append(Prefix, checksummedAddr...)) + + checksummedAddr = append(checksummedAddr, checksum[0:2]...) + + return multichain.Address(base58.Encode(checksummedAddr)), nil +} diff --git a/chain/acala/address_test.go b/chain/acala/address_test.go new file mode 100644 index 00000000..1242c6b6 --- /dev/null +++ b/chain/acala/address_test.go @@ -0,0 +1,36 @@ +package acala_test + +import ( + "github.com/centrifuge/go-substrate-rpc-client/signature" + "github.com/renproject/multichain/api/address" + "github.com/renproject/multichain/chain/acala" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Address", func() { + addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.AddressTypeDefault) + + Context("when encoding raw address", func() { + It("should match the human-readable address", func() { + addr, err := addrEncodeDecoder.EncodeAddress(address.RawAddress(signature.TestKeyringPairAlice.PublicKey)) + Expect(err).NotTo(HaveOccurred()) + Expect(addr).To(Equal(address.Address(signature.TestKeyringPairAlice.Address))) + rawAddr, err := addrEncodeDecoder.DecodeAddress(addr) + Expect(err).NotTo(HaveOccurred()) + Expect(rawAddr).To(Equal(address.RawAddress(signature.TestKeyringPairAlice.PublicKey))) + }) + }) + + Context("when decoding human-readable address", func() { + It("should match the raw address", func() { + rawAddr, err := addrEncodeDecoder.DecodeAddress(address.Address(signature.TestKeyringPairAlice.Address)) + Expect(err).NotTo(HaveOccurred()) + Expect(rawAddr).To(Equal(address.RawAddress(signature.TestKeyringPairAlice.PublicKey))) + addr, err := addrEncodeDecoder.EncodeAddress(rawAddr) + Expect(err).NotTo(HaveOccurred()) + Expect(addr).To(Equal(address.Address(signature.TestKeyringPairAlice.Address))) + }) + }) +}) diff --git a/chain/acala/contract.go b/chain/acala/contract.go new file mode 100644 index 00000000..82183b80 --- /dev/null +++ b/chain/acala/contract.go @@ -0,0 +1,101 @@ +package acala + +import ( + "context" + "encoding/binary" + "fmt" + + "github.com/centrifuge/go-substrate-rpc-client/types" + "github.com/renproject/multichain/api/address" + "github.com/renproject/multichain/api/contract" + "github.com/renproject/pack" + "github.com/renproject/surge" +) + +// BurnCallContractInput is the input structure that is consumed in a serialized +// byte form by the contract call API to fetch Acala's burn logs. +type BurnCallContractInput struct { + Nonce pack.U32 +} + +// BurnCallContractOutput is the output structure that is returned in a +// serialized byte form by the contract call API. It contains all the fields +// specific to the burn log at the given burn count (nonce). +type BurnCallContractOutput struct { + Amount pack.U256 + Recipient address.RawAddress + Confs pack.U64 + Payload pack.Bytes +} + +// BurnEventData defines the data stored as burn logs when RenBTC tokens are +// burnt on Acala. +type BurnEventData struct { + BlockNumber types.U32 + Recipient types.Bytes + Amount types.U128 +} + +// CallContract implements the multichain.ContractCaller interface for Acala. It +// is used specifically for fetching burn logs from Acala's storage. The input +// calldata is serialized nonce (burn count) of RenVmBridge, and it returns +// the serialized byte form of the respected burn log along with the number of +// block confirmations of that burn. +func (client *Client) CallContract(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { + // Deserialise the calldata bytes. + input := BurnCallContractInput{} + if err := surge.FromBinary(&input, calldata); err != nil { + return pack.Bytes{}, fmt.Errorf("deserialise calldata: %v", err) + } + + // Get chain metadata. + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) + } + + nonceBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(nonceBytes, uint32(input.Nonce)) + + // This key is used to read the state storage at the block of interest. + key, err := types.CreateStorageKey(meta, "Template", "BurnEvents", nonceBytes, nil) + if err != nil { + return pack.Bytes{}, fmt.Errorf("create storage key: %v", err) + } + + // Retrieve and decode bytes from storage at the block and storage key. + burnEventData := BurnEventData{} + ok, err := client.api.RPC.State.GetStorageLatest(key, &burnEventData) + if err != nil || !ok { + return pack.Bytes{}, fmt.Errorf("get storage: %v", err) + } + + // Get the latest block header. This will be used to calculate number of block + // confirmations of the burn log of interest. + header, err := client.api.RPC.Chain.GetHeaderLatest() + if err != nil { + return pack.Bytes{}, fmt.Errorf("get header: %v", err) + } + + blockhash, err := client.api.RPC.Chain.GetBlockHash(uint64(burnEventData.BlockNumber)) + if err != nil { + return pack.Bytes{}, fmt.Errorf("get blockhash: %v", err) + } + + // Calculate block confirmations for the event. + confs := types.U32(header.Number) - burnEventData.BlockNumber + 1 + + burnLogOutput := BurnCallContractOutput{ + Amount: pack.NewU256FromInt(burnEventData.Amount.Int), + Recipient: address.RawAddress(burnEventData.Recipient), + Confs: pack.NewU64(uint64(confs)), + Payload: pack.Bytes(blockhash[:]), + } + + out, err := surge.ToBinary(burnLogOutput) + if err != nil { + return pack.Bytes{}, fmt.Errorf("serialise output: %v", err) + } + + return pack.Bytes(out), nil +} diff --git a/chain/acala/mint_burn.go b/chain/acala/mint_burn.go new file mode 100644 index 00000000..e0462623 --- /dev/null +++ b/chain/acala/mint_burn.go @@ -0,0 +1,172 @@ +package acala + +import ( + "fmt" + "time" + + "github.com/centrifuge/go-substrate-rpc-client/signature" + "github.com/centrifuge/go-substrate-rpc-client/types" + "github.com/renproject/pack" +) + +// To returns the default recipient of the newly minted tokens in Acala. We use +// Alice's address as this recipient. +func (client *Client) To() (pack.String, pack.Bytes) { + return pack.String(signature.TestKeyringPairAlice.Address), pack.Bytes(signature.TestKeyringPairAlice.PublicKey) +} + +// Mint consumes a RenVM mint signature and parameters, constructs an unsigned +// extrinsic to mint new RenBTC tokens for minterKey and broadcasts this +// extrinsic. It returns the extrinsic hash on successful execution. +func (client *Client) Mint(minterKey signature.KeyringPair, phash, nhash pack.Bytes32, sig pack.Bytes65, amount uint64) (pack.Bytes32, error) { + opts := types.SerDeOptions{NoPalletIndices: true} + types.SetSerDeOptions(opts) + + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return pack.Bytes32{}, fmt.Errorf("get metadata: %v", err) + } + + alice := types.NewAddressFromAccountID(minterKey.PublicKey) + c, err := types.NewCall(meta, "RenVmBridge.mint", alice, phash, types.NewUCompactFromUInt(amount), nhash, sig) + if err != nil { + return pack.Bytes32{}, fmt.Errorf("construct call: %v", err) + } + + hash, err := client.api.RPC.Author.SubmitExtrinsic(types.NewExtrinsic(c)) + if err != nil { + return pack.Bytes32{}, fmt.Errorf("submit extrinsic: %v", err) + } + + return pack.NewBytes32(hash), nil +} + +// Burn broadcasts a signed extrinsic to burn RenBTC tokens from burnerKey on +// Acala. It returns the hash (of the block it was included in), the nonce +// (burn count) and the extrinsic's signature. +func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes, amount uint64) (pack.Bytes32, pack.U32, pack.Bytes, error) { + opts := types.SerDeOptions{NoPalletIndices: false} + types.SetSerDeOptions(opts) + + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get metadata: %v", err) + } + + c, err := types.NewCall(meta, "RenVmBridge.burn", types.Bytes(recipient), types.NewUCompactFromUInt(amount)) + if err != nil { + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("construct call: %v", err) + } + + ext := types.NewExtrinsic(c) + + genesisHash, err := client.api.RPC.Chain.GetBlockHash(0) + if err != nil { + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get blockhash: %v", err) + } + + rv, err := client.api.RPC.State.GetRuntimeVersionLatest() + if err != nil { + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get runtime version: %v", err) + } + + key, err := types.CreateStorageKey(meta, "System", "Account", burnerKey.PublicKey, nil) + if err != nil { + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("create storage key: %v", err) + } + + var accountInfo types.AccountInfo + ok, err := client.api.RPC.State.GetStorageLatest(key, &accountInfo) + if err != nil || !ok { + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get storage: %v", err) + } + + nonce := uint32(accountInfo.Nonce) + + o := types.SignatureOptions{ + BlockHash: genesisHash, + Era: types.ExtrinsicEra{IsMortalEra: false}, + GenesisHash: genesisHash, + Nonce: types.NewUCompactFromUInt(uint64(nonce)), + SpecVersion: rv.SpecVersion, + Tip: types.NewUCompactFromUInt(0), + TransactionVersion: rv.TransactionVersion, + } + + err = ext.Sign(burnerKey, o) + if err != nil { + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("sign extrinsic: %v", err) + } + + sub, err := client.api.RPC.Author.SubmitAndWatchExtrinsic(ext) + if err != nil { + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("submit extrinsic: %v", err) + } + defer sub.Unsubscribe() + + timeout := time.After(10 * time.Second) + for { + select { + case status := <-sub.Chan(): + if status.IsInBlock { + nonce, err := client.Nonce() + if err != nil { + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get nonce: %v", err) + } + return pack.NewBytes32(status.AsInBlock), nonce.Sub(pack.U32(1)), pack.Bytes(ext.Signature.Signature.AsSr25519[:]), nil + } + case <-timeout: + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("timeout on tx confirmation") + } + } +} + +// TokenAccount represents the token balance information of an address. +type TokenAccount struct { + Free types.U128 + Reserved types.U128 + Frozen types.U128 +} + +// Balance returns the RenBTC free balance of an address. +func (client *Client) Balance(user signature.KeyringPair) (pack.U256, error) { + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return pack.U256{}, fmt.Errorf("get metadata: %v", err) + } + + key, err := types.CreateStorageKey(meta, "Tokens", "Accounts", user.PublicKey, []byte{0, 5}) + if err != nil { + return pack.U256{}, fmt.Errorf("create storage key: %v", err) + } + + var data TokenAccount + ok, err := client.api.RPC.State.GetStorageLatest(key, &data) + if err != nil || !ok { + return pack.U256{}, fmt.Errorf("get storage: %v", err) + } + + return pack.NewU256FromInt(data.Free.Int), nil +} + +// Nonce returns the burn count in RenVmBridge. This is an identifier used to +// fetch burn logs from Acala's storage. +func (client *Client) Nonce() (pack.U32, error) { + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return pack.U32(0), fmt.Errorf("get metadata: %v", err) + } + + key, err := types.CreateStorageKey(meta, "Template", "NextBurnEventId", nil, nil) + if err != nil { + return pack.U32(0), fmt.Errorf("create storage key: %v", err) + } + + var data types.U32 + ok, err := client.api.RPC.State.GetStorageLatest(key, &data) + if err != nil || !ok { + return pack.U32(0), fmt.Errorf("get storage: %v", err) + } + + return pack.U32(data), nil +} diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go new file mode 100644 index 00000000..67d088e9 --- /dev/null +++ b/chain/acala/mint_burn_test.go @@ -0,0 +1,182 @@ +package acala_test + +import ( + "context" + "encoding/hex" + "fmt" + "math/rand" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/centrifuge/go-substrate-rpc-client/signature" + "github.com/ethereum/go-ethereum/crypto" + "github.com/renproject/id" + "github.com/renproject/multichain" + "github.com/renproject/multichain/api/contract" + "github.com/renproject/multichain/chain/acala" + "github.com/renproject/multichain/chain/bitcoin" + "github.com/renproject/multichain/chain/ethereum" + "github.com/renproject/pack" + "github.com/renproject/surge" +) + +var _ = Describe("Mint Burn", func() { + r := rand.New(rand.NewSource(GinkgoRandomSeed())) + + blockhash, nonce, extSign := pack.Bytes32{}, pack.U32(0), pack.Bytes{} + balanceBefore, balanceAfter := pack.U256{}, pack.U256{} + + client, err := acala.NewClient(acala.DefaultClientOptions()) + Expect(err).NotTo(HaveOccurred()) + + alice, phash, nhash, sig, mintAmount, burnAmount, recipient := constructMintParams(r) + + Context("when minting over renbridge", func() { + It("should succeed", func() { + balanceBefore, err = client.Balance(alice) + if err != nil { + // This means there are no tokens allocated for that address. + Expect(err).To(Equal(fmt.Errorf("get storage: "))) + balanceBefore = pack.NewU256FromUint64(uint64(0)) + } + + _, err = client.Mint(alice, phash, nhash, sig, mintAmount) + Expect(err).NotTo(HaveOccurred()) + + time.Sleep(5 * time.Second) + + balanceAfter, err = client.Balance(alice) + Expect(err).NotTo(HaveOccurred()) + + Expect(balanceBefore.Add(pack.NewU256FromUint64(mintAmount))).To(Equal(balanceAfter)) + }) + }) + + Context("when burning over renbridge", func() { + It("should succeed", func() { + balanceBefore, err = client.Balance(alice) + Expect(err).NotTo(HaveOccurred()) + + blockhash, nonce, extSign, err = client.Burn(alice, recipient, burnAmount) + Expect(err).NotTo(HaveOccurred()) + + time.Sleep(5 * time.Second) + + balanceAfter, err = client.Balance(alice) + Expect(err).NotTo(HaveOccurred()) + + Expect(balanceBefore.Sub(pack.NewU256FromUint64(burnAmount))).To(Equal(balanceAfter)) + }) + }) + + Context("when reading burn info", func() { + Context("when reading from storage", func() { + It("should succeed", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + input := acala.BurnCallContractInput{Nonce: nonce} + calldata, err := surge.ToBinary(input) + Expect(err).NotTo(HaveOccurred()) + outputBytes, err := client.CallContract(ctx, multichain.Address(""), contract.CallData(calldata)) + Expect(err).NotTo(HaveOccurred()) + + output := acala.BurnCallContractOutput{} + Expect(surge.FromBinary(&output, outputBytes)).To(Succeed()) + + Expect(output.Amount).To(Equal(pack.NewU256FromUint64(burnAmount))) + Expect(output.Recipient).To(Equal(multichain.RawAddress(recipient))) + Expect(output.Confs).To(BeNumerically(">", 0)) + }) + }) + + Context("when reading from system events", func() { + It("should succeed", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + input := acala.BurnLogInput{ + Blockhash: blockhash, + ExtSign: extSign, + } + calldata, err := surge.ToBinary(input) + Expect(err).NotTo(HaveOccurred()) + outputBytes, err := client.CallContractSystemEvents(ctx, multichain.Address(""), contract.CallData(calldata)) + Expect(err).NotTo(HaveOccurred()) + + output := acala.BurnLogOutput{} + Expect(surge.FromBinary(&output, outputBytes)).To(Succeed()) + + Expect(output.Amount).To(Equal(pack.NewU256FromUint64(burnAmount))) + Expect(output.Recipient).To(Equal(multichain.RawAddress(recipient))) + Expect(output.Confs).To(BeNumerically(">", 0)) + }) + }) + }) +}) + +func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pack.Bytes32, pack.Bytes65, uint64, uint64, pack.Bytes) { + // Get RenVM priv key. + renVmPrivKeyBytes, err := hex.DecodeString("c44700049a72c02bbacbec25551190427315f046c1f656f23884949da3fbdc3a") + Expect(err).NotTo(HaveOccurred()) + renVmPrivKey := id.PrivKey{} + err = surge.FromBinary(&renVmPrivKey, renVmPrivKeyBytes) + Expect(err).NotTo(HaveOccurred()) + + // Get random pHash and nHash. + phashBytes := make([]byte, 32) + nhashBytes := make([]byte, 32) + _, err = r.Read(phashBytes) + Expect(err).NotTo(HaveOccurred()) + _, err = r.Read(nhashBytes) + Expect(err).NotTo(HaveOccurred()) + + // Amount to be minted/burnt. + // Mint amount [80000, 100000] + // Burn amount [20000, 50000] + mintAmount := uint64(r.Intn(20000) + 80000) + burnAmount := uint64(r.Intn(30000) + 20000) + + // Selector for this cross-chain mint. + selector := []byte("BTC/toAcala") + shash32 := [32]byte{} + copy(shash32[:], crypto.Keccak256(selector)) + + // Initialise message args + sighash32 := [32]byte{} + phash32 := [32]byte{} + nhash32 := [32]byte{} + to := [32]byte{} + addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.AddressTypeDefault) + rawAddr, err := addrEncodeDecoder.DecodeAddress(multichain.Address(signature.TestKeyringPairAlice.Address)) + Expect(err).NotTo(HaveOccurred()) + + // Get message sighash. + copy(to[:], rawAddr) + copy(phash32[:], phashBytes) + copy(nhash32[:], nhashBytes) + copy(sighash32[:], crypto.Keccak256(ethereum.Encode( + pack.Bytes32(phash32), + pack.NewU256FromUint64(mintAmount), + pack.Bytes32(shash32), + pack.Bytes32(to), + pack.Bytes32(nhash32), + ))) + + // Sign the sighash. + hash := id.Hash(sighash32) + sig65, err := renVmPrivKey.Sign(&hash) + Expect(err).NotTo(HaveOccurred()) + sig65[64] = sig65[64] + 27 + + // Get the address of the burn recipient. + recipientAddr := multichain.Address(pack.String("miMi2VET41YV1j6SDNTeZoPBbmH8B4nEx6")) + btcEncodeDecoder := bitcoin.NewAddressEncodeDecoder(&chaincfg.RegressionNetParams) + recipientRawAddr, err := btcEncodeDecoder.DecodeAddress(recipientAddr) + Expect(err).NotTo(HaveOccurred()) + + return signature.TestKeyringPairAlice, pack.Bytes32(phash32), pack.Bytes32(nhash32), pack.Bytes65(sig65), mintAmount, burnAmount, pack.Bytes(recipientRawAddr) +} diff --git a/chain/acala/ren_vm_bridge_types.go b/chain/acala/ren_vm_bridge_types.go new file mode 100644 index 00000000..dfdfeb95 --- /dev/null +++ b/chain/acala/ren_vm_bridge_types.go @@ -0,0 +1,51 @@ +package acala + +import "github.com/centrifuge/go-substrate-rpc-client/types" + +type eventMinted struct { + Phase types.Phase + Owner types.AccountID + Amount types.U128 + Topics []types.Hash +} + +type eventBurnt struct { + Phase types.Phase + Owner types.AccountID + Dest types.Bytes + Amount types.U128 + Topics []types.Hash +} + +type eventDeposited struct { + Phase types.Phase + CurrencyId [2]byte + Who types.AccountID + Amount types.U128 + Topics []types.Hash +} + +type eventWithdrawn struct { + Phase types.Phase + CurrencyId [2]byte + Who types.AccountID + Amount types.U128 + Topics []types.Hash +} + +type eventTreasury struct { + Phase types.Phase + Deposit types.U128 + Topics []types.Hash +} + +// RenVmBridgeEvents represents all of the system events that could be emitted +// along with extrinsics specific to the RenVmBridge module. +type RenVmBridgeEvents struct { + types.EventRecords + Currencies_Deposited []eventDeposited + RenVmBridge_Minted []eventMinted + Currencies_Withdrawn []eventWithdrawn + RenVmBridge_Burnt []eventBurnt + AcalaTreasury_Deposit []eventTreasury +} diff --git a/chain/ethereum/account.go b/chain/ethereum/account.go new file mode 100644 index 00000000..0ef7c7c4 --- /dev/null +++ b/chain/ethereum/account.go @@ -0,0 +1,151 @@ +package ethereum + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" + "github.com/renproject/multichain/api/account" + "github.com/renproject/multichain/api/address" + "github.com/renproject/multichain/api/contract" + "github.com/renproject/pack" +) + +type TxBuilder struct { + config *params.ChainConfig +} + +func NewTxBuilder(config *params.ChainConfig) TxBuilder { + return TxBuilder{config: config} +} + +func (txBuilder TxBuilder) BuildTx( + from, to address.Address, + value, nonce pack.U256, + gasPrice, gasLimit pack.U256, + payload pack.Bytes, +) (account.Tx, error) { + toAddr, err := NewAddressFromHex(string(to)) + if err != nil { + return nil, fmt.Errorf("decoding address: %v", err) + } + fromAddr, err := NewAddressFromHex(string(from)) + if err != nil { + return nil, fmt.Errorf("decoding address: %v", err) + } + + tx := types.NewTransaction(nonce.Int().Uint64(), common.Address(toAddr), value.Int(), gasLimit.Int().Uint64(), gasPrice.Int(), []byte(payload)) + + signer := types.MakeSigner(txBuilder.config, nil) + signed := false + + return &Tx{fromAddr, tx, signer, signed}, nil +} + +type Tx struct { + from Address + + tx *types.Transaction + + signer types.Signer + signed bool +} + +func (tx *Tx) Hash() pack.Bytes { + return pack.NewBytes(tx.tx.Hash().Bytes()) +} + +func (tx *Tx) From() address.Address { + return address.Address(tx.from.String()) +} + +func (tx *Tx) To() address.Address { + return address.Address(tx.tx.To().String()) +} + +func (tx *Tx) Value() pack.U256 { + return pack.NewU256FromInt(tx.tx.Value()) +} + +func (tx *Tx) Nonce() pack.U256 { + return pack.NewU256FromU64(pack.NewU64(tx.tx.Nonce())) +} + +func (tx *Tx) Payload() contract.CallData { + return contract.CallData(pack.NewBytes(tx.tx.Data())) +} + +func (tx *Tx) Sighashes() ([]pack.Bytes32, error) { + sighash := tx.signer.Hash(tx.tx) + return []pack.Bytes32{pack.NewBytes32(sighash)}, nil +} + +func (tx *Tx) Sign(signatures []pack.Bytes65, pubKey pack.Bytes) error { + if tx.signed { + return fmt.Errorf("already signed") + } + + if len(signatures) != 1 { + return fmt.Errorf("expected 1 signature, found: %v", len(signatures)) + } + + signedTx, err := tx.tx.WithSignature(tx.signer, signatures[0].Bytes()) + if err != nil { + return err + } + + tx.tx = signedTx + tx.signed = true + return nil +} + +func (tx *Tx) Serialize() (pack.Bytes, error) { + serialized, err := tx.tx.MarshalJSON() + if err != nil { + return pack.Bytes{}, err + } + + return pack.NewBytes(serialized), nil +} + +type EthClient struct { + client *ethclient.Client +} + +func NewClient(rpcURL pack.String) (*EthClient, error) { + client, err := ethclient.Dial(string(rpcURL)) + if err != nil { + return nil, fmt.Errorf("dialing RPC URL %v: %v", rpcURL, err) + } + + return &EthClient{client}, nil +} + +func (client EthClient) Tx(ctx context.Context, txId pack.Bytes) (account.Tx, pack.U64, error) { + txHash := common.BytesToHash(txId) + tx, isPending, err := client.client.TransactionByHash(ctx, txHash) + if err != nil { + return nil, pack.NewU64(0), fmt.Errorf("fetching tx: %v", err) + } + if isPending { + return nil, pack.NewU64(0), fmt.Errorf("tx not confirmed") + } + txReceipt, err := client.client.TransactionReceipt(ctx, txHash) + if err != nil { + return nil, pack.NewU64(0), fmt.Errorf("fetching tx receipt: %v", err) + } + block, err := client.client.BlockByNumber(ctx, nil) + if err != nil { + return nil, pack.NewU64(0), fmt.Errorf("fetching current block: %v", err) + } + confs := block.NumberU64() - txReceipt.BlockNumber.Uint64() + 1 + + return &Tx{tx: tx}, pack.NewU64(confs), nil +} + +func (client EthClient) SubmitTx(ctx context.Context, tx account.Tx) error { + panic("unimplemented") +} diff --git a/chain/ethereum/gas.go b/chain/ethereum/gas.go new file mode 100644 index 00000000..f22e568b --- /dev/null +++ b/chain/ethereum/gas.go @@ -0,0 +1,32 @@ +package ethereum + +import ( + "context" + + "github.com/renproject/pack" +) + +// A GasEstimator returns the gas price (in wei) that is needed in order to +// confirm transactions with an estimated maximum delay of one block. In +// distributed networks that collectively build, sign, and submit transactions, +// it is important that all nodes in the network have reached consensus on the +// gas price. +type GasEstimator struct { + wei pack.U256 +} + +// NewGasEstimator returns a simple gas estimator that always returns the given +// gas price (in wei) to be used for broadcasting an Ethereum transaction. +func NewGasEstimator(wei pack.U256) GasEstimator { + return GasEstimator{ + wei: wei, + } +} + +// EstimateGas returns the number of wei that is needed in order to confirm +// transactions with an estimated maximum delay of one block. It is the +// responsibility of the caller to know the number of bytes in their +// transaction. +func (gasEstimator GasEstimator) EstimateGas(_ context.Context) (pack.U256, pack.U256, error) { + return gasEstimator.wei, gasEstimator.wei, nil +} diff --git a/go.mod b/go.mod index cd2b81b0..d037561a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/btcsuite/btcd v0.21.0-beta github.com/btcsuite/btcutil v1.0.2 - github.com/centrifuge/go-substrate-rpc-client v1.1.0 + github.com/centrifuge/go-substrate-rpc-client v2.0.0+incompatible github.com/codahale/blake2 v0.0.0-20150924215134-8d10d0420cbf github.com/cosmos/cosmos-sdk v0.39.1 github.com/ethereum/go-ethereum v1.9.20 @@ -18,6 +18,7 @@ require ( github.com/multiformats/go-varint v0.0.6 github.com/onsi/ginkgo v1.14.0 github.com/onsi/gomega v1.10.1 + github.com/pierrec/xxHash v0.1.5 // indirect github.com/renproject/id v0.4.2 github.com/renproject/pack v0.2.5 github.com/renproject/surge v1.2.6 diff --git a/go.sum b/go.sum index 2df88715..5aefbad4 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,7 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.1.3 h1:mEV3iyZWjkxQ7R8ia8GcG97vCX5zQQ7n4o8R2BylwQY= github.com/99designs/keyring v1.1.3/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904= @@ -78,6 +79,7 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A= github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -132,7 +134,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/centrifuge/go-substrate-rpc-client v1.1.0/go.mod h1:GBMLH8MQs5g4FcrytcMm9uRgBnTL1LIkNTue6lUPhZU= +github.com/centrifuge/go-substrate-rpc-client v2.0.0+incompatible h1:FvPewruOgelqA/DVBdX7/Q6znUGGQ+g0ciG5tA2Fk98= +github.com/centrifuge/go-substrate-rpc-client v2.0.0+incompatible/go.mod h1:GBMLH8MQs5g4FcrytcMm9uRgBnTL1LIkNTue6lUPhZU= github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -192,6 +195,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e h1:lj77EKYUpYXTd8CD/+QMIf8b6OIOTsfEBSXiAzuEHTU= @@ -336,9 +340,9 @@ github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNI github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus v0.0.0-20190402143921-271e53dc4968/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -439,7 +443,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= @@ -1187,6 +1190,8 @@ github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/xxHash v0.1.5 h1:n/jBpwTHiER4xYvK3/CdPVnLDPchj8eTJFFLUb4QHBo= +github.com/pierrec/xxHash v0.1.5/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -1266,6 +1271,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -1278,6 +1284,7 @@ github.com/sercand/kuberesolver v2.1.0+incompatible/go.mod h1:lWF3GL0xptCB/vCiJP github.com/sercand/kuberesolver v2.4.0+incompatible/go.mod h1:lWF3GL0xptCB/vCiJPl/ZshwPsX/n4Y7u0CW9E7aQIQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= @@ -1835,6 +1842,7 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/infra/acala/Dockerfile b/infra/acala/Dockerfile index a988fb74..ed3498ee 100644 --- a/infra/acala/Dockerfile +++ b/infra/acala/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:xenial +FROM ubuntu:bionic RUN apt-get update && apt-get install --yes --fix-missing software-properties-common curl git clang RUN apt-get install --yes --fix-missing --no-install-recommends build-essential @@ -13,17 +13,17 @@ RUN git clone https://github.com/AcalaNetwork/Acala.git RUN mv Acala /app WORKDIR /app -# TEMPORARY: use the branch that has a good reference to the submodules -# TODO: remove when the `master` branch of Acala is updated -RUN git fetch -RUN git checkout update-orml -RUN git pull - # Make sure submodule.recurse is set to true to make life with submodule easier. +RUN git fetch && \ + git pull origin master && \ + git checkout a92256960d27ff9c9428b07b947c7e5ef132fedd RUN git config --global submodule.recurse true # Build -RUN make init +RUN rustup default nightly-2020-09-27 +RUN rustup target add wasm32-unknown-unknown --toolchain nightly-2020-09-27 +RUN make submodule +RUN make build-full RUN make build WORKDIR / diff --git a/infra/acala/run.sh b/infra/acala/run.sh index a48318f1..789f8132 100644 --- a/infra/acala/run.sh +++ b/infra/acala/run.sh @@ -3,7 +3,7 @@ ADDRESS=$1 # Start cd /app -make run +SKIP_WASM_BUILD= cargo run --features with-acala-runtime -- --dev --execution=native -lruntime=debug --ws-external --rpc-external sleep 10 # Print setup diff --git a/multichain.go b/multichain.go index 436a7fc0..3f84ac0c 100644 --- a/multichain.go +++ b/multichain.go @@ -254,7 +254,7 @@ func (chain Chain) ChainType() ChainType { switch chain { case Bitcoin, BitcoinCash, DigiByte, Dogecoin, Zcash: return ChainTypeUTXOBased - case BinanceSmartChain, Ethereum, Filecoin, Terra: + case Acala, BinanceSmartChain, Ethereum, Filecoin, Terra: return ChainTypeAccountBased // These chains are handled separately because they are mock chains. These