github.com/DapperCollectives/CAST/backend@v0.0.0-20230921221157-1350c8be7c96/main/shared/flow.go (about)

     1  package shared
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"flag"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/rs/zerolog/log"
    17  
    18  	"github.com/onflow/cadence"
    19  	"github.com/onflow/flow-go-sdk"
    20  	"github.com/onflow/flow-go-sdk/client"
    21  	"google.golang.org/grpc"
    22  )
    23  
    24  type FlowAdapter struct {
    25  	Config           FlowConfig
    26  	ArchiveClient    *client.Client
    27  	LiveClient       *client.Client
    28  	Context          context.Context
    29  	CustomScriptsMap map[string]CustomScript
    30  	URL              string
    31  	ArchiveURL       string
    32  	Env              string
    33  }
    34  
    35  type FlowContract struct {
    36  	Source  string            `json:"source,omitempty"`
    37  	Aliases map[string]string `json:"aliases"`
    38  }
    39  
    40  type FlowConfig struct {
    41  	Contracts map[string]FlowContract `json:"contracts"`
    42  	Networks  map[string]string       `json:"networks"`
    43  }
    44  
    45  type Contract struct {
    46  	Name           *string  `json:"name,omitempty"`
    47  	Addr           *string  `json:"addr,omitempty"`
    48  	Public_path    *string  `json:"publicPath,omitempty"`
    49  	Threshold      *float64 `json:"threshold,omitempty,string"`
    50  	MaxWeight      *float64 `json:"maxWeight,omitempty,string"`
    51  	Float_event_id *uint64  `json:"floatEventId,omitempty,string"`
    52  	Script         *string  `json:"script,omitempty"`
    53  }
    54  
    55  var (
    56  	placeholderTokenName            = regexp.MustCompile(`"[^"\s]*TOKEN_NAME"`)
    57  	placeholderTokenAddr            = regexp.MustCompile(`"[^"\s]*TOKEN_ADDRESS"`)
    58  	placeholderFungibleTokenAddr    = regexp.MustCompile(`"[^"\s]*FUNGIBLE_TOKEN_ADDRESS"`)
    59  	placeholderNonFungibleTokenAddr = regexp.MustCompile(`"[^"\s]*NON_FUNGIBLE_TOKEN_ADDRESS"`)
    60  	placeholderMetadataViewsAddr    = regexp.MustCompile(`"[^"\s]*METADATA_VIEWS_ADDRESS"`)
    61  	placeholderCollectionPublicPath = regexp.MustCompile(`"[^"\s]*COLLECTION_PUBLIC_PATH"`)
    62  	placeholderTopshotAddr          = regexp.MustCompile(`"[^"\s]*TOPSHOT_ADDRESS"`)
    63  )
    64  
    65  func NewFlowClient(flowEnv string, customScriptsMap map[string]CustomScript) *FlowAdapter {
    66  	adapter := FlowAdapter{}
    67  	adapter.Context = context.Background()
    68  	adapter.Env = flowEnv
    69  	adapter.CustomScriptsMap = customScriptsMap
    70  	path := "./flow.json"
    71  
    72  	content, err := ioutil.ReadFile(path)
    73  
    74  	if err != nil {
    75  		log.Fatal().Msgf("Error when opening file: %+v.", err)
    76  	}
    77  
    78  	var config FlowConfig
    79  	err = json.Unmarshal(content, &config)
    80  	if err != nil {
    81  		log.Fatal().Msgf("Error parsing flow.json: %+v.", err)
    82  	}
    83  
    84  	adapter.Config = config
    85  	adapter.URL = config.Networks[adapter.Env]
    86  	adapter.ArchiveURL = config.Networks[fmt.Sprintf("%s_archive", adapter.Env)]
    87  
    88  	// Explicitly set when running test suite
    89  	if flag.Lookup("test.v") != nil {
    90  		adapter.URL = "127.0.0.1:3569"
    91  		adapter.ArchiveURL = "127.0.0.1:3569"
    92  	}
    93  
    94  	log.Info().Msgf("FLOW URL: %s", adapter.URL)
    95  	// create flow client
    96  	FlowClient, err := client.New(adapter.URL, grpc.WithInsecure())
    97  	if err != nil {
    98  		log.Panic().Msgf("Failed to connect to %s.", adapter.URL)
    99  	}
   100  
   101  	log.Info().Msgf("FLOW Archive URL: %s", adapter.ArchiveURL)
   102  
   103  	//create archive client
   104  	FlowClientArchive, err := client.New(adapter.ArchiveURL, grpc.WithInsecure())
   105  	if err != nil {
   106  		log.Panic().Msgf("Failed to connect to %s.", adapter.ArchiveURL)
   107  	}
   108  
   109  	adapter.LiveClient = FlowClient
   110  	adapter.ArchiveClient = FlowClientArchive
   111  
   112  	return &adapter
   113  }
   114  
   115  func (fa *FlowAdapter) GetAccountAtBlockHeight(addr string, blockheight uint64) (*flow.Account, error) {
   116  	hexAddr := flow.HexToAddress(addr)
   117  	return fa.ArchiveClient.GetAccountAtBlockHeight(fa.Context, hexAddr, blockheight)
   118  }
   119  
   120  func (fa *FlowAdapter) GetCurrentBlockHeight() (int, error) {
   121  	block, err := fa.LiveClient.GetLatestBlock(fa.Context, true)
   122  	if err != nil {
   123  		return 0, err
   124  	}
   125  	return int(block.Height), nil
   126  }
   127  
   128  func (fa *FlowAdapter) GetAddressBalanceAtBlockHeight(addr string, blockHeight uint64, balanceResponse *FTBalanceResponse, contract *Contract) error {
   129  
   130  	if *contract.Name == "FlowToken" {
   131  		balances, err := fa.GetFlowBalance(addr, blockHeight)
   132  		fmt.Println(balances)
   133  		if err != nil {
   134  			return err
   135  		}
   136  		balanceResponse.PrimaryAccountBalance = uint64(balances[0] * 100000000.0)
   137  		balanceResponse.SecondaryAccountBalance = uint64(balances[1] * 10000000.0)
   138  		balanceResponse.StakingBalance = uint64(balances[2] * 10000000.0)
   139  
   140  		return nil
   141  
   142  	} else {
   143  		balance, err := fa.GetFTBalance(addr, blockHeight, *contract.Name, *contract.Addr, *contract.Public_path)
   144  		fmt.Println(balance)
   145  		if err != nil {
   146  			return err
   147  		}
   148  		balanceResponse.PrimaryAccountBalance = uint64(balance * 100000000.0)
   149  		balanceResponse.SecondaryAccountBalance = 0
   150  		balanceResponse.StakingBalance = 0
   151  		return nil
   152  	}
   153  }
   154  
   155  func (fa *FlowAdapter) ValidateSignature(address, message string, sigs *[]CompositeSignature, messageType string) error {
   156  	log.Debug().Msgf("ValidateSignature()\nAddress: %s\nMessage: %s\nSigs: %v.", address, message, *sigs)
   157  
   158  	// Prepare Script Args
   159  	flowAddress := flow.HexToAddress(address)
   160  	cadenceAddress := cadence.NewAddress(flowAddress)
   161  	cadenceString, err := cadence.NewString(message)
   162  
   163  	// Pull out signature strings + keyIds for script
   164  	var cadenceSigs []cadence.Value = make([]cadence.Value, len(*sigs))
   165  	var cadenceKeyIds []cadence.Value = make([]cadence.Value, len(*sigs))
   166  	for i, cSig := range *sigs {
   167  		cadenceKeyIds[i] = cadence.NewInt(int(cSig.Key_id))
   168  		cadenceSigs[i] = cadence.String(cSig.Signature)
   169  	}
   170  
   171  	var domainSeparationTag string
   172  	if messageType == "TRANSACTION" {
   173  		domainSeparationTag = "FLOW-V0.0-transaction"
   174  	} else {
   175  		domainSeparationTag = "FLOW-V0.0-user"
   176  	}
   177  
   178  	// Load script
   179  	script, err := ioutil.ReadFile("./main/cadence/scripts/validate_signature.cdc")
   180  
   181  	if err != nil {
   182  		log.Error().Err(err).Msgf("Error reading cadence script file.")
   183  		return err
   184  	}
   185  
   186  	// call the script to verify the signature on chain
   187  	value, err := fa.LiveClient.ExecuteScriptAtLatestBlock(
   188  		fa.Context,
   189  		script,
   190  		[]cadence.Value{
   191  			cadenceAddress,
   192  			cadence.NewArray(cadenceKeyIds),
   193  			cadence.NewArray(cadenceSigs),
   194  			cadence.String(cadenceString),
   195  			cadence.String(domainSeparationTag),
   196  		},
   197  	)
   198  
   199  	log.Info().Msgf("Validate signature script returned: %v", value)
   200  
   201  	if err != nil && strings.Contains(err.Error(), "ledger returns unsuccessful") {
   202  		log.Error().Err(err).Msg("signature validation error")
   203  		return errors.New("flow access node error, please cast your vote again")
   204  	} else if err != nil {
   205  		log.Error().Err(err).Msg("Signature validation error.")
   206  		return err
   207  	}
   208  
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	if value != cadence.NewBool(true) {
   214  		return errors.New("invalid signature")
   215  	}
   216  
   217  	return nil
   218  
   219  }
   220  
   221  func (fa *FlowAdapter) EnforceTokenThreshold(scriptPath, creatorAddr string, c *Contract) (bool, error) {
   222  
   223  	var balance float64
   224  	flowAddress := flow.HexToAddress(creatorAddr)
   225  	cadenceAddress := cadence.NewAddress(flowAddress)
   226  	cadencePath := cadence.Path{Domain: "public", Identifier: *c.Public_path}
   227  	fmt.Println(cadencePath)
   228  	script, err := ioutil.ReadFile(scriptPath)
   229  	if err != nil {
   230  		log.Error().Err(err).Msgf("Error reading cadence script file.")
   231  		return false, err
   232  	}
   233  
   234  	var cadenceValue cadence.Value
   235  
   236  	if scriptPath == "./main/cadence/scripts/get_nfts_ids.cdc" {
   237  		isFungible := false
   238  		script = fa.ReplaceContractPlaceholders(string(script[:]), c, isFungible)
   239  
   240  		//call the non-fungible token script to verify balance
   241  		cadenceValue, err = fa.LiveClient.ExecuteScriptAtLatestBlock(
   242  			fa.Context,
   243  			script,
   244  			[]cadence.Value{
   245  				cadenceAddress,
   246  			})
   247  		if err != nil {
   248  			log.Error().Err(err).Msg("Error executing Non-Fungible-Token script.")
   249  			return false, err
   250  		}
   251  		value := CadenceValueToInterface(cadenceValue)
   252  
   253  		nftIds := value.([]interface{})
   254  		balance = float64(len(nftIds))
   255  
   256  	} else {
   257  		isFungible := true
   258  		script = fa.ReplaceContractPlaceholders(string(script[:]), c, isFungible)
   259  
   260  		//call the fungible-token script to verify balance
   261  		cadenceValue, err = fa.LiveClient.ExecuteScriptAtLatestBlock(
   262  			fa.Context,
   263  			script,
   264  			[]cadence.Value{
   265  				cadencePath,
   266  				cadenceAddress,
   267  			})
   268  		if err != nil {
   269  			log.Error().Err(err).Msg("Error executing Fungible-Token Script.")
   270  			return false, err
   271  		}
   272  
   273  		value := CadenceValueToInterface(cadenceValue)
   274  		balance, err = strconv.ParseFloat(value.(string), 64)
   275  		if err != nil {
   276  			log.Error().Err(err).Msg("Error converting cadence value to float.")
   277  			return false, err
   278  		}
   279  	}
   280  
   281  	//check if balance is greater than threshold
   282  	if balance < *c.Threshold {
   283  		return false, nil
   284  	}
   285  
   286  	return true, nil
   287  }
   288  
   289  func (fa *FlowAdapter) GetFlowBalance(address string, blockHeight uint64) ([]float64, error) {
   290  	flowAddress := flow.HexToAddress(address)
   291  	cadenceAddress := cadence.NewAddress(flowAddress)
   292  	script, err := ioutil.ReadFile("./main/cadence/scripts/get_total_balance.cdc")
   293  	if err != nil {
   294  		log.Error().Err(err).Msgf("Error reading cadence script file.")
   295  		return []float64{0, 0, 0}, err
   296  	}
   297  	cadenceValue, err := fa.ArchiveClient.ExecuteScriptAtBlockHeight(
   298  		fa.Context,
   299  		blockHeight,
   300  		script,
   301  		[]cadence.Value{
   302  			cadenceAddress,
   303  		},
   304  	)
   305  
   306  	if err != nil {
   307  		log.Error().Err(err).Msg("Error executing Total balance Script.")
   308  		return []float64{0, 0, 0}, err
   309  	}
   310  
   311  	var values []interface{} = CadenceValueToInterface(cadenceValue).([]interface{})
   312  
   313  	balancePrimary, err := strconv.ParseFloat(values[0].(string), 64)
   314  	if err != nil {
   315  		log.Error().Err(err).Msg("Error converting cadence value to float. (balancePrimary)")
   316  		return []float64{0, 0, 0}, err
   317  	}
   318  
   319  	balanceSecondary, err := strconv.ParseFloat(values[1].(string), 64)
   320  	if err != nil {
   321  		log.Error().Err(err).Msg("Error converting cadence value to float. (balanceSecondary)")
   322  		return []float64{0, 0, 0}, err
   323  	}
   324  
   325  	balanceStaked, err := strconv.ParseFloat(values[2].(string), 64)
   326  	if err != nil {
   327  		log.Error().Err(err).Msg("Error converting cadence value to float. (balanceStaked)")
   328  		return []float64{0, 0, 0}, err
   329  	}
   330  	return []float64{balancePrimary, balanceSecondary, balanceStaked}, nil
   331  }
   332  
   333  // @bluesign: this is called via archival node now
   334  func (fa *FlowAdapter) GetFTBalance(address string, blockHeight uint64, contractName string, contractAddress string, publicPath string) (float64, error) {
   335  	flowAddress := flow.HexToAddress(address)
   336  	cadenceAddress := cadence.NewAddress(flowAddress)
   337  
   338  	script, err := ioutil.ReadFile("./main/cadence/scripts/get_balance.cdc")
   339  	if err != nil {
   340  		log.Error().Err(err).Msgf("Error reading cadence script file.")
   341  		return 0, err
   342  	}
   343  
   344  	dummyContract := Contract{
   345  		Name:        &contractName,
   346  		Public_path: &publicPath,
   347  		Addr:        &contractAddress,
   348  	}
   349  
   350  	script = fa.ReplaceContractPlaceholders(string(script[:]), &dummyContract, true)
   351  	cadencePath := cadence.Path{Domain: "public", Identifier: *dummyContract.Public_path}
   352  	cadenceValue, err := fa.ArchiveClient.ExecuteScriptAtBlockHeight(
   353  		fa.Context,
   354  		blockHeight,
   355  		script,
   356  		[]cadence.Value{
   357  			cadencePath,
   358  			cadenceAddress,
   359  		})
   360  	if err != nil {
   361  		log.Error().Err(err).Msg("Error executing Funigble-Token Script.")
   362  		return 0, err
   363  	}
   364  
   365  	value := CadenceValueToInterface(cadenceValue)
   366  	balance, err := strconv.ParseFloat(value.(string), 64)
   367  	if err != nil {
   368  		log.Error().Err(err).Msg("Error converting cadence value to float.")
   369  		return 0, err
   370  	}
   371  
   372  	return balance, nil
   373  }
   374  
   375  func (fa *FlowAdapter) GetNFTIds(voterAddr string, c *Contract, path string) ([]interface{}, error) {
   376  	flowAddress := flow.HexToAddress(voterAddr)
   377  	cadenceAddress := cadence.NewAddress(flowAddress)
   378  
   379  	script, err := ioutil.ReadFile(path)
   380  	if err != nil {
   381  		log.Error().Err(err).Msgf("Error reading cadence script file.")
   382  		return nil, err
   383  	}
   384  
   385  	script = fa.ReplaceContractPlaceholders(string(script[:]), c, false)
   386  
   387  	cadenceValue, err := fa.LiveClient.ExecuteScriptAtLatestBlock(
   388  		fa.Context,
   389  		script,
   390  		[]cadence.Value{
   391  			cadenceAddress,
   392  		},
   393  	)
   394  	if err != nil {
   395  		log.Error().Err(err).Msg("Error executing script.")
   396  		return nil, err
   397  	}
   398  
   399  	value := CadenceValueToInterface(cadenceValue)
   400  
   401  	nftIds := value.([]interface{})
   402  	return nftIds, nil
   403  }
   404  
   405  func (fa *FlowAdapter) GetFloatNFTIds(voterAddr string, c *Contract) ([]interface{}, error) {
   406  	flowAddress := flow.HexToAddress(voterAddr)
   407  	cadenceAddress := cadence.NewAddress(flowAddress)
   408  	cadenceUInt64 := cadence.NewUInt64(*c.Float_event_id)
   409  
   410  	script, err := ioutil.ReadFile("./main/cadence/float/scripts/get_float_ids.cdc")
   411  	if err != nil {
   412  		log.Error().Err(err).Msgf("Error reading cadence script file.")
   413  		return nil, err
   414  	}
   415  
   416  	script = fa.ReplaceContractPlaceholders(string(script[:]), c, false)
   417  
   418  	cadenceValue, err := fa.LiveClient.ExecuteScriptAtLatestBlock(
   419  		fa.Context,
   420  		script,
   421  		[]cadence.Value{
   422  			cadenceAddress,
   423  			cadenceUInt64,
   424  		})
   425  	if err != nil {
   426  		log.Error().Err(err).Msg("Error executing script.")
   427  		return nil, err
   428  	}
   429  
   430  	value := CadenceValueToInterface(cadenceValue)
   431  
   432  	nftIds := value.([]interface{})
   433  	return nftIds, nil
   434  }
   435  
   436  func (fa *FlowAdapter) CheckIfUserHasEvent(voterAddr string, c *Contract) (bool, error) {
   437  	flowAddress := flow.HexToAddress(voterAddr)
   438  	cadenceAddress := cadence.NewAddress(flowAddress)
   439  	cadenceUInt64 := cadence.NewUInt64(*c.Float_event_id)
   440  
   441  	script, err := ioutil.ReadFile("./main/cadence/float/scripts/owns_specific_float.cdc")
   442  	if err != nil {
   443  		log.Error().Err(err).Msgf("Error reading cadence script file.")
   444  		return false, err
   445  	}
   446  
   447  	script = fa.ReplaceContractPlaceholders(string(script[:]), c, false)
   448  
   449  	cadenceValue, err := fa.LiveClient.ExecuteScriptAtLatestBlock(
   450  		fa.Context,
   451  		script,
   452  		[]cadence.Value{
   453  			cadenceAddress,
   454  			cadenceUInt64,
   455  		})
   456  	if err != nil {
   457  		log.Error().Err(err).Msg("Error executing script.")
   458  		return false, err
   459  	}
   460  
   461  	value := CadenceValueToInterface(cadenceValue)
   462  
   463  	hasEventNFT := false
   464  	if value == "true" {
   465  		hasEventNFT = true
   466  	}
   467  	return hasEventNFT, nil
   468  }
   469  
   470  func (fa *FlowAdapter) GetEventNFT(voterAddr string, c *Contract) (interface{}, error) {
   471  
   472  	//first we get all the floats a user owns
   473  
   474  	flowAddress := flow.HexToAddress(voterAddr)
   475  	cadenceAddress := cadence.NewAddress(flowAddress)
   476  	cadenceUInt64 := cadence.NewUInt64(*c.Float_event_id)
   477  
   478  	script, err := ioutil.ReadFile("./main/cadence/float/scripts/get_specific_float.cdc")
   479  	if err != nil {
   480  		log.Error().Err(err).Msgf("Error reading cadence script file.")
   481  		return nil, err
   482  	}
   483  
   484  	script = fa.ReplaceContractPlaceholders(string(script[:]), c, false)
   485  
   486  	cadenceValue, err := fa.LiveClient.ExecuteScriptAtLatestBlock(
   487  		fa.Context,
   488  		script,
   489  		[]cadence.Value{
   490  			cadenceAddress,
   491  			cadenceUInt64,
   492  		})
   493  	if err != nil {
   494  		log.Error().Err(err).Msg("Error executing script.")
   495  		return nil, err
   496  	}
   497  
   498  	value := CadenceValueToInterface(cadenceValue)
   499  
   500  	return value, nil
   501  }
   502  
   503  func (fa *FlowAdapter) ReplaceContractPlaceholders(code string, c *Contract, isFungible bool) []byte {
   504  	var (
   505  		fungibleTokenAddr    string
   506  		nonFungibleTokenAddr string
   507  		metadataViewsAddr    string
   508  		topshotAddr          string
   509  	)
   510  
   511  	nonFungibleTokenAddr = fa.Config.Contracts["NonFungibleToken"].Aliases[os.Getenv("FLOW_ENV")]
   512  	fungibleTokenAddr = fa.Config.Contracts["FungibleToken"].Aliases[os.Getenv("FLOW_ENV")]
   513  	metadataViewsAddr = fa.Config.Contracts["MetadataViews"].Aliases[os.Getenv("FLOW_ENV")]
   514  	topshotAddr = fa.Config.Contracts["TopShot"].Aliases[os.Getenv("FLOW_ENV")]
   515  
   516  	if isFungible {
   517  		code = placeholderFungibleTokenAddr.ReplaceAllString(code, fungibleTokenAddr)
   518  	} else {
   519  		code = placeholderCollectionPublicPath.ReplaceAllString(code, *c.Public_path)
   520  		code = placeholderNonFungibleTokenAddr.ReplaceAllString(code, nonFungibleTokenAddr)
   521  	}
   522  
   523  	code = placeholderMetadataViewsAddr.ReplaceAllString(code, metadataViewsAddr)
   524  	code = placeholderTokenName.ReplaceAllString(code, *c.Name)
   525  	code = placeholderTokenAddr.ReplaceAllString(code, *c.Addr)
   526  	code = placeholderTopshotAddr.ReplaceAllString(code, topshotAddr)
   527  
   528  	return []byte(code)
   529  }
   530  
   531  func WaitForSeal(
   532  	ctx context.Context,
   533  	c *client.Client,
   534  	id flow.Identifier,
   535  ) (*flow.TransactionResult, *flow.Transaction, error) {
   536  	result, err := c.GetTransactionResult(ctx, id)
   537  	if err != nil {
   538  		return nil, nil, err
   539  	}
   540  
   541  	for result.Status != flow.TransactionStatusSealed {
   542  		time.Sleep(time.Second)
   543  		result, err = c.GetTransactionResult(ctx, id)
   544  		if err != nil {
   545  			return nil, nil, err
   546  		}
   547  	}
   548  
   549  	tx, errTx := c.GetTransaction(ctx, id)
   550  	return result, tx, errTx
   551  }
   552  
   553  func CadenceValueToInterface(field cadence.Value) interface{} {
   554  	if field == nil {
   555  		return ""
   556  	}
   557  
   558  	switch field := field.(type) {
   559  	case cadence.Optional:
   560  		return CadenceValueToInterface(field.Value)
   561  	case cadence.Dictionary:
   562  		result := map[string]interface{}{}
   563  		for _, item := range field.Pairs {
   564  			key, err := strconv.Unquote(item.Key.String())
   565  			if err != nil {
   566  				result[item.Key.String()] = CadenceValueToInterface(item.Value)
   567  				continue
   568  			}
   569  
   570  			result[key] = CadenceValueToInterface(item.Value)
   571  		}
   572  		return result
   573  	case cadence.Struct:
   574  		result := map[string]interface{}{}
   575  		subStructNames := field.StructType.Fields
   576  
   577  		for j, subField := range field.Fields {
   578  			result[subStructNames[j].Identifier] = CadenceValueToInterface(subField)
   579  		}
   580  		return result
   581  	case cadence.Array:
   582  		var result []interface{}
   583  		for _, item := range field.Values {
   584  			result = append(result, CadenceValueToInterface(item))
   585  		}
   586  		return result
   587  	default:
   588  		result, err := strconv.Unquote(field.String())
   589  		if err != nil {
   590  			return field.String()
   591  		}
   592  		return result
   593  	}
   594  }
   595  
   596  func FloatBalanceToUint(balance float64) uint64 {
   597  	return uint64(balance * 1000000)
   598  }