From 54ef823458e2162a7a1d82e64fac69c86146d077 Mon Sep 17 00:00:00 2001 From: Nicko Guyer Date: Mon, 25 Apr 2022 16:24:18 -0400 Subject: [PATCH] Make ERC-20 the default token connector Signed-off-by: Nicko Guyer --- cmd/init.go | 2 +- .../blockchain/ethereum/besu/besu_provider.go | 2 +- internal/blockchain/ethereum/contracts.go | 27 +++++-- .../blockchain/ethereum/ethconnect/client.go | 12 +-- .../blockchain/ethereum/geth/geth_provider.go | 2 +- internal/stacks/stack_manager.go | 10 ++- internal/tokens/erc1155/contracts.go | 38 ++++++++-- internal/tokens/erc1155/erc1155_provider.go | 3 +- internal/tokens/erc20erc721/contracts.go | 75 +++++++++++++++---- .../erc20erc721/erc20_erc721_provider.go | 3 +- internal/tokens/tokens_provider.go | 2 +- internal/tokens/types.go | 22 ++++++ 12 files changed, 158 insertions(+), 40 deletions(-) create mode 100644 internal/tokens/types.go diff --git a/cmd/init.go b/cmd/init.go index 065b19ce..5e53614d 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -187,7 +187,7 @@ func init() { initCmd.Flags().IntVarP(&initOptions.ServicesBasePort, "services-base-port", "s", 5100, "Mapped port base of services (100 added for each member)") initCmd.Flags().StringVarP(&databaseSelection, "database", "d", "sqlite3", fmt.Sprintf("Database type to use. Options are: %v", types.DBSelectionStrings)) initCmd.Flags().StringVarP(&blockchainProviderInput, "blockchain-provider", "b", "geth", fmt.Sprintf("Blockchain provider to use. Options are: %v", types.BlockchainProviderStrings)) - initCmd.Flags().StringArrayVarP(&tokenProvidersSelection, "token-providers", "t", []string{"erc1155"}, fmt.Sprintf("Token providers to use. Options are: %v", types.ValidTokenProviders)) + initCmd.Flags().StringArrayVarP(&tokenProvidersSelection, "token-providers", "t", []string{"erc20_erc721"}, fmt.Sprintf("Token providers to use. Options are: %v", types.ValidTokenProviders)) initCmd.Flags().IntVarP(&initOptions.ExternalProcesses, "external", "e", 0, "Manage a number of FireFly core processes outside of the docker-compose stack - useful for development and debugging") initCmd.Flags().StringVarP(&initOptions.FireFlyVersion, "release", "r", "latest", "Select the FireFly release version to use") initCmd.Flags().StringVarP(&initOptions.ManifestPath, "manifest", "m", "", "Path to a manifest.json file containing the versions of each FireFly microservice to use. Overrides the --release flag.") diff --git a/internal/blockchain/ethereum/besu/besu_provider.go b/internal/blockchain/ethereum/besu/besu_provider.go index 5607259c..45951252 100644 --- a/internal/blockchain/ethereum/besu/besu_provider.go +++ b/internal/blockchain/ethereum/besu/besu_provider.go @@ -223,7 +223,7 @@ func (p *BesuProvider) Reset() error { } func (p *BesuProvider) GetContracts(filename string, extraArgs []string) ([]string, error) { - contracts, err := ethereum.ReadCombinedABIJSON(filename) + contracts, err := ethereum.ReadContractJSON(filename) if err != nil { return []string{}, err } diff --git a/internal/blockchain/ethereum/contracts.go b/internal/blockchain/ethereum/contracts.go index f4742bb8..93cf67f2 100644 --- a/internal/blockchain/ethereum/contracts.go +++ b/internal/blockchain/ethereum/contracts.go @@ -33,11 +33,12 @@ type CompiledContract struct { } type truffleCompiledContract struct { - ABI interface{} `json:"abi"` - Bytecode string `json:"bytecode"` + ABI interface{} `json:"abi"` + Bytecode string `json:"bytecode"` + ContractName string `json:"contractName"` } -func ReadTruffleCompiledContract(filePath string) (*CompiledContract, error) { +func ReadTruffleCompiledContract(filePath string) (*CompiledContracts, error) { d, _ := ioutil.ReadFile(filePath) var truffleCompiledContract *truffleCompiledContract err := json.Unmarshal(d, &truffleCompiledContract) @@ -48,10 +49,15 @@ func ReadTruffleCompiledContract(filePath string) (*CompiledContract, error) { ABI: truffleCompiledContract.ABI, Bytecode: truffleCompiledContract.Bytecode, } - return contract, nil + contracts := &CompiledContracts{ + Contracts: map[string]*CompiledContract{ + truffleCompiledContract.ContractName: contract, + }, + } + return contracts, nil } -func ReadCombinedABIJSON(filePath string) (*CompiledContracts, error) { +func ReadSolcCompiledContract(filePath string) (*CompiledContracts, error) { d, _ := ioutil.ReadFile(filePath) var contracts *CompiledContracts err := json.Unmarshal(d, &contracts) @@ -61,6 +67,17 @@ func ReadCombinedABIJSON(filePath string) (*CompiledContracts, error) { return contracts, nil } +func ReadContractJSON(filePath string) (*CompiledContracts, error) { + contracts, err := ReadSolcCompiledContract(filePath) + if err != nil { + return nil, err + } + if len(contracts.Contracts) > 0 { + return contracts, nil + } + return ReadTruffleCompiledContract(filePath) +} + func ExtractContracts(containerName, sourceDir, destinationDir string, verbose bool) error { if err := docker.RunDockerCommand(destinationDir, verbose, verbose, "cp", containerName+":"+sourceDir, destinationDir); err != nil { return err diff --git a/internal/blockchain/ethereum/ethconnect/client.go b/internal/blockchain/ethereum/ethconnect/client.go index 5cf9b293..5bc545da 100644 --- a/internal/blockchain/ethereum/ethconnect/client.go +++ b/internal/blockchain/ethereum/ethconnect/client.go @@ -301,15 +301,17 @@ func DeployFireFlyContract(s *types.Stack, log log.Logger, verbose bool) (*core. return nil, err } - contracts, err := ethereum.ReadCombinedABIJSON(filepath.Join(s.RuntimeDir, "contracts", "Firefly.json")) + var fireflyContract *ethereum.CompiledContract + contracts, err := ethereum.ReadContractJSON(filepath.Join(s.RuntimeDir, "contracts", "Firefly.json")) if err != nil { return nil, err } + fireflyContract, ok := contracts.Contracts["Firefly.sol:Firefly"] if !ok { - fireflyContract, err = ethereum.ReadTruffleCompiledContract(filepath.Join(s.RuntimeDir, "contracts", "Firefly.json")) - if err != nil { - return nil, err + fireflyContract, ok = contracts.Contracts["Firefly"] + if !ok { + return nil, fmt.Errorf("unable to find compiled FireFly contract") } } @@ -328,7 +330,7 @@ func DeployFireFlyContract(s *types.Stack, log log.Logger, verbose bool) (*core. } func DeployCustomContract(member *types.Member, filename, contractName string) (string, error) { - contracts, err := ethereum.ReadCombinedABIJSON(filename) + contracts, err := ethereum.ReadContractJSON(filename) if err != nil { return "", nil } diff --git a/internal/blockchain/ethereum/geth/geth_provider.go b/internal/blockchain/ethereum/geth/geth_provider.go index d9ffff40..198d3159 100644 --- a/internal/blockchain/ethereum/geth/geth_provider.go +++ b/internal/blockchain/ethereum/geth/geth_provider.go @@ -205,7 +205,7 @@ func (p *GethProvider) Reset() error { } func (p *GethProvider) GetContracts(filename string, extraArgs []string) ([]string, error) { - contracts, err := ethereum.ReadCombinedABIJSON(filename) + contracts, err := ethereum.ReadContractJSON(filename) if err != nil { return []string{}, err } diff --git a/internal/stacks/stack_manager.go b/internal/stacks/stack_manager.go index 6e93ad01..818a781b 100644 --- a/internal/stacks/stack_manager.go +++ b/internal/stacks/stack_manager.go @@ -775,9 +775,17 @@ func (s *StackManager) runFirstTimeSetup(verbose bool, options *types.StartOptio } for i, tp := range s.tokenProviders { - if err := tp.DeploySmartContracts(i); err != nil { + result, err := tp.DeploySmartContracts(i) + if err != nil { return err } + if result != nil { + msg := result.GetTokenDeploymentMessage() + if msg != "" { + // TODO: move this to the end somehow + fmt.Println(msg) + } + } } if s.Stack.ContractAddress == "" { diff --git a/internal/tokens/erc1155/contracts.go b/internal/tokens/erc1155/contracts.go index 5ad25902..a48fb6c5 100644 --- a/internal/tokens/erc1155/contracts.go +++ b/internal/tokens/erc1155/contracts.go @@ -24,12 +24,26 @@ import ( "github.com/hyperledger/firefly-cli/internal/blockchain/ethereum" "github.com/hyperledger/firefly-cli/internal/blockchain/ethereum/ethconnect" "github.com/hyperledger/firefly-cli/internal/log" + "github.com/hyperledger/firefly-cli/internal/tokens" "github.com/hyperledger/firefly-cli/pkg/types" ) const TOKEN_URI_PATTERN = "firefly://token/{id}" -func DeployContracts(s *types.Stack, log log.Logger, verbose bool, tokenIndex int) error { +type TokenDeploymentResult struct { + Message string + Result interface{} +} + +func (t *TokenDeploymentResult) GetTokenDeploymentMessage() string { + return t.Message +} + +func (t *TokenDeploymentResult) GetTokenDeploymentResult() interface{} { + return t.Result +} + +func DeployContracts(s *types.Stack, log log.Logger, verbose bool, tokenIndex int) (tokens.ITokenDeploymentResult, error) { var containerName string for _, member := range s.Members { if !member.External { @@ -38,17 +52,21 @@ func DeployContracts(s *types.Stack, log log.Logger, verbose bool, tokenIndex in } } if containerName == "" { - return errors.New("unable to extract contracts from container - no valid tokens containers found in stack") + return nil, errors.New("unable to extract contracts from container - no valid tokens containers found in stack") } log.Info("extracting smart contracts") if err := ethereum.ExtractContracts(containerName, "/root/contracts", s.RuntimeDir, verbose); err != nil { - return err + return nil, err } - tokenContract, err := ethereum.ReadTruffleCompiledContract(filepath.Join(s.RuntimeDir, "contracts", "ERC1155MixedFungible.json")) + contracts, err := ethereum.ReadTruffleCompiledContract(filepath.Join(s.RuntimeDir, "contracts", "ERC1155MixedFungible.json")) if err != nil { - return err + return nil, err + } + tokenContract, ok := contracts.Contracts["ERC1155MixedFungible"] + if !ok { + return nil, fmt.Errorf("unable to find ERC1155MixedFungible in compiled contracts") } var tokenContractAddress string @@ -58,16 +76,20 @@ func DeployContracts(s *types.Stack, log log.Logger, verbose bool, tokenIndex in log.Info(fmt.Sprintf("deploying ERC1155 contract on '%s'", member.ID)) tokenContractAddress, err = ethconnect.DeprecatedDeployContract(member, tokenContract, "erc1155", map[string]string{"uri": TOKEN_URI_PATTERN}) if err != nil { - return err + return nil, err } } else { log.Info(fmt.Sprintf("registering ERC1155 contract on '%s'", member.ID)) err = ethconnect.DeprecatedRegisterContract(member, tokenContract, tokenContractAddress, "erc1155", map[string]string{"uri": TOKEN_URI_PATTERN}) if err != nil { - return err + return nil, err } } } - return nil + result := &TokenDeploymentResult{ + Message: "i deployed an ERC1155 contract", + } + + return result, nil } diff --git a/internal/tokens/erc1155/erc1155_provider.go b/internal/tokens/erc1155/erc1155_provider.go index c6153d59..bbacab10 100644 --- a/internal/tokens/erc1155/erc1155_provider.go +++ b/internal/tokens/erc1155/erc1155_provider.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly-cli/internal/core" "github.com/hyperledger/firefly-cli/internal/docker" "github.com/hyperledger/firefly-cli/internal/log" + "github.com/hyperledger/firefly-cli/internal/tokens" "github.com/hyperledger/firefly-cli/pkg/types" ) @@ -31,7 +32,7 @@ type ERC1155Provider struct { Stack *types.Stack } -func (p *ERC1155Provider) DeploySmartContracts(tokenIndex int) error { +func (p *ERC1155Provider) DeploySmartContracts(tokenIndex int) (tokens.ITokenDeploymentResult, error) { return DeployContracts(p.Stack, p.Log, p.Verbose, tokenIndex) } diff --git a/internal/tokens/erc20erc721/contracts.go b/internal/tokens/erc20erc721/contracts.go index 8ac06ada..3aa4d553 100644 --- a/internal/tokens/erc20erc721/contracts.go +++ b/internal/tokens/erc20erc721/contracts.go @@ -18,22 +18,67 @@ package erc20erc721 import ( "github.com/hyperledger/firefly-cli/internal/log" + "github.com/hyperledger/firefly-cli/internal/tokens" "github.com/hyperledger/firefly-cli/pkg/types" ) -func DeployContracts(s *types.Stack, log log.Logger, verbose bool, tokenIndex int) error { - - // Currently the act of creating and deploying a suitable ERC20 or ERC721 compliant - // contract, or contract factory, is an exercise left to the user. - // - // For users simply experimenting with how tokens work, the ERC1155 standard is recommended - // as a flexbile and fully formed sample implementation of fungible and non-fungible tokens - // with a set of features you would expect. - // - // For users looking to take the next step and create a "proper" coin or NFT collection, - // you really can't bypass the step of investigating the right OpenZeppelin (or other) - // base class and determining the tokenomics (around supply / minting / burning / governance) - // on top of that base class using the examples and standards out there. - - return nil +type TokenDeploymentResult struct { + Message string + Result interface{} +} + +func (t *TokenDeploymentResult) GetTokenDeploymentMessage() string { + return t.Message +} + +func (t *TokenDeploymentResult) GetTokenDeploymentResult() interface{} { + return t.Result +} + +func DeployContracts(s *types.Stack, log log.Logger, verbose bool, tokenIndex int) (tokens.ITokenDeploymentResult, error) { + // var containerName string + // for _, member := range s.Members { + // if !member.External { + // containerName = fmt.Sprintf("%s_tokens_%s_%d", s.Name, member.ID, tokenIndex) + // break + // } + // } + // if containerName == "" { + // return nil, errors.New("unable to extract contracts from container - no valid tokens containers found in stack") + // } + // log.Info("extracting smart contracts") + + // if err := ethereum.ExtractContracts(containerName, "/root/contracts", s.RuntimeDir, verbose); err != nil { + // return nil, err + // } + + // tokenContract, err := ethereum.ReadSolcCompiledContract(filepath.Join(s.RuntimeDir, "contracts", "TokenFactory.json")) + // if err != nil { + // return nil, err + // } + + // var tokenContractAddress string + // for _, member := range s.Members { + // // TODO: move to address based contract deployment, once ERC-1155 connector is updated to not require an EthConnect REST API registration + // if tokenContractAddress == "" { + // log.Info(fmt.Sprintf("deploying ERC1155 contract on '%s'", member.ID)) + // tokenContractAddress, err = ethconnect.DeprecatedDeployContract(member, tokenContract, "ERC20WithData", map[string]string{"uri": TOKEN_URI_PATTERN}) + // if err != nil { + // return nil, err + // } + // } else { + // log.Info(fmt.Sprintf("registering ERC1155 contract on '%s'", member.ID)) + // err = ethconnect.DeprecatedRegisterContract(member, tokenContract, tokenContractAddress, "erc1155", map[string]string{"uri": TOKEN_URI_PATTERN}) + // if err != nil { + // return nil, err + // } + // } + // } + + // deploymentResult := &TokenDeploymentResult{ + // Message: "hey, I deployed a contract!", + // } + + // return deploymentResult, nil + return nil, nil } diff --git a/internal/tokens/erc20erc721/erc20_erc721_provider.go b/internal/tokens/erc20erc721/erc20_erc721_provider.go index c33b46a9..a50bae01 100644 --- a/internal/tokens/erc20erc721/erc20_erc721_provider.go +++ b/internal/tokens/erc20erc721/erc20_erc721_provider.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly-cli/internal/core" "github.com/hyperledger/firefly-cli/internal/docker" "github.com/hyperledger/firefly-cli/internal/log" + "github.com/hyperledger/firefly-cli/internal/tokens" "github.com/hyperledger/firefly-cli/pkg/types" ) @@ -31,7 +32,7 @@ type ERC20ERC721Provider struct { Stack *types.Stack } -func (p *ERC20ERC721Provider) DeploySmartContracts(tokenIndex int) error { +func (p *ERC20ERC721Provider) DeploySmartContracts(tokenIndex int) (tokens.ITokenDeploymentResult, error) { return DeployContracts(p.Stack, p.Log, p.Verbose, tokenIndex) } diff --git a/internal/tokens/tokens_provider.go b/internal/tokens/tokens_provider.go index 09f835f2..86007823 100644 --- a/internal/tokens/tokens_provider.go +++ b/internal/tokens/tokens_provider.go @@ -23,7 +23,7 @@ import ( ) type ITokensProvider interface { - DeploySmartContracts(tokenIndex int) error + DeploySmartContracts(tokenIndex int) (ITokenDeploymentResult, error) FirstTimeSetup(tokenIdx int) error GetDockerServiceDefinitions(tokenIdx int) []*docker.ServiceDefinition GetFireflyConfig(m *types.Member, tokenIdx int) *core.TokenConnector diff --git a/internal/tokens/types.go b/internal/tokens/types.go new file mode 100644 index 00000000..accd66d6 --- /dev/null +++ b/internal/tokens/types.go @@ -0,0 +1,22 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tokens + +type ITokenDeploymentResult interface { + GetTokenDeploymentMessage() string + GetTokenDeploymentResult() interface{} +}