Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ install-tools:

.PHONY: test
test:
GO111MODULE=on go test -coverprofile=$(COVER_PROFILE) $(if $(JSON_OUTPUT),-json,) ./...
CGO_ENABLED=1 CGO_CFLAGS="-O2 -D__BLST_PORTABLE__ -std=gnu11" GO111MODULE=on go test -coverprofile=$(COVER_PROFILE) $(if $(JSON_OUTPUT),-json,) ./...

.PHONY: test-e2e-emulator
test-e2e-emulator:
Expand All @@ -53,7 +53,7 @@ ci: install-tools generate test coverage

$(BINARY):
CGO_ENABLED=1 \
CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" \
CGO_CFLAGS="-O2 -D__BLST_PORTABLE__ -std=gnu11" \
GO111MODULE=on go build \
-trimpath \
-ldflags \
Expand Down
132 changes: 119 additions & 13 deletions internal/schedule/cancel.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@
package schedule

import (
"context"
"fmt"
"strconv"

"github.com/onflow/cadence"
flowsdk "github.com/onflow/flow-go-sdk"
"github.com/spf13/cobra"

"github.com/onflow/flowkit/v2"
"github.com/onflow/flowkit/v2/accounts"
"github.com/onflow/flowkit/v2/output"
"github.com/onflow/flowkit/v2/transactions"

"github.com/onflow/flow-cli/common/branding"
"github.com/onflow/flow-cli/internal/command"
"github.com/onflow/flow-cli/internal/util"
)
Expand Down Expand Up @@ -71,42 +78,141 @@ func cancelRun(
return nil, fmt.Errorf("transaction ID is required as an argument")
}

transactionID := args[0]
transactionIDStr := args[0]

// Parse transaction ID as UInt64
transactionID, err := strconv.ParseUint(transactionIDStr, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid transaction ID: %w", err)
}

signer, err := util.GetSignerAccount(state, cancelFlags.Signer)
if err != nil {
return nil, err
}

logger.Info(fmt.Sprintf("Network: %s", globalFlags.Network))
logger.Info(fmt.Sprintf("Signer: %s (%s)", cancelFlags.Signer, signer.Address.String()))
logger.Info(fmt.Sprintf("Transaction ID: %s", transactionID))
chainID, err := util.NetworkToChainID(globalFlags.Network)
if err != nil {
return nil, err
}

if chainID == flowsdk.Mainnet {
return nil, fmt.Errorf("transaction scheduling is not yet supported on mainnet")
}

schedulerUtilsAddress, err := getContractAddress(FlowTransactionSchedulerUtils, chainID)
if err != nil {
return nil, err
}

flowTokenAddress, err := getContractAddress(FlowToken, chainID)
if err != nil {
return nil, err
}

fungibleTokenAddress, err := getContractAddress(FungibleToken, chainID)
if err != nil {
return nil, err
}

networkStr := branding.GrayStyle.Render(globalFlags.Network)
addressStr := branding.PurpleStyle.Render(signer.Address.HexWithPrefix())
signerStr := branding.GrayStyle.Render(cancelFlags.Signer)
txIDStr := branding.PurpleStyle.Render(transactionIDStr)

logger.Info("Canceling scheduled transaction...")
logger.Info("")
logger.Info(fmt.Sprintf("🌐 Network: %s", networkStr))
logger.Info(fmt.Sprintf("📝 Signer: %s (%s)", signerStr, addressStr))
logger.Info(fmt.Sprintf("🔍 Transaction ID: %s", txIDStr))
logger.Info("")

// Build transaction code
cancelTx := fmt.Sprintf(`import FlowTransactionSchedulerUtils from %s
import FlowToken from %s
import FungibleToken from %s

transaction(transactionId: UInt64) {
let manager: auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}
let tokenReceiver: &{FungibleToken.Receiver}

prepare(signer: auth(BorrowValue) &Account) {
// 1. Borrow Manager reference
self.manager = signer.storage.borrow<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>(
from: FlowTransactionSchedulerUtils.managerStoragePath
) ?? panic("Could not borrow Manager. Please ensure you have a Manager set up.")

// Verify transaction exists in manager
assert(
self.manager.getTransactionIDs().contains(transactionId),
message: "Transaction with ID ".concat(transactionId.toString()).concat(" not found in manager")
)

// 2. Get FlowToken receiver to deposit refunds
self.tokenReceiver = signer.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
.borrow()
?? panic("Could not borrow FlowToken receiver")
}

execute {
// Cancel the transaction and receive refunded fees
let refundVault <- self.manager.cancel(id: transactionId)

// Deposit refunded fees back to the account
self.tokenReceiver.deposit(from: <-refundVault)
}
}`, schedulerUtilsAddress, flowTokenAddress, fungibleTokenAddress)

_, txResult, err := flow.SendTransaction(
context.Background(),
transactions.AccountRoles{
Proposer: *signer,
Authorizers: []accounts.Account{*signer},
Payer: *signer,
},
flowkit.Script{
Code: []byte(cancelTx),
Args: []cadence.Value{cadence.NewUInt64(transactionID)},
},
1000,
)

if err != nil {
return nil, fmt.Errorf("failed to cancel scheduled transaction: %w", err)
}

if txResult.Error != nil {
return nil, fmt.Errorf("cancel transaction failed: %s", txResult.Error.Error())
}

// TODO: Implement cancel logic for scheduled transaction
logger.Info("")
successIcon := branding.GreenStyle.Render("✅")
successMsg := branding.GreenStyle.Render("Scheduled transaction canceled successfully")
logger.Info(fmt.Sprintf("%s %s", successIcon, successMsg))

return &cancelResult{
success: true,
message: fmt.Sprintf("Scheduled transaction %s canceled successfully", transactionID),
success: true,
transactionID: transactionIDStr,
}, nil
}

type cancelResult struct {
success bool
message string
success bool
transactionID string
}

func (r *cancelResult) JSON() any {
return map[string]any{
"success": r.success,
"message": r.message,
"success": r.success,
"transactionID": r.transactionID,
"message": "Scheduled transaction canceled successfully",
}
}

func (r *cancelResult) String() string {
return r.message
return ""
}

func (r *cancelResult) Oneliner() string {
return r.message
return fmt.Sprintf("Scheduled transaction %s canceled successfully", r.transactionID)
}
36 changes: 30 additions & 6 deletions internal/schedule/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ import (
"fmt"

flowsdk "github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go/fvm/systemcontracts"
flowGo "github.com/onflow/flow-go/model/flow"
)

// ContractName represents a scheduler-related contract name
type ContractName string

const (
// FlowTransactionSchedulerUtils contract provides utility functions for transaction scheduling
FlowTransactionSchedulerUtils ContractName = "FlowTransactionSchedulerUtils"
// FlowTransactionScheduler contract handles the core transaction scheduling logic
FlowTransactionScheduler ContractName = "FlowTransactionScheduler"
FlowTransactionScheduler ContractName = "FlowTransactionScheduler"
FlowToken ContractName = "FlowToken"
FungibleToken ContractName = "FungibleToken"
)

// contractAddresses maps contract names to their addresses on different networks
Expand All @@ -48,12 +50,34 @@ var contractAddresses = map[ContractName]map[flowsdk.ChainID]string{

// getContractAddress returns the contract address for the given contract name and network
func getContractAddress(contract ContractName, chainID flowsdk.ChainID) (string, error) {
// Check if mainnet
if chainID == flowsdk.Mainnet {
// Check if mainnet for scheduling contracts
if chainID == flowsdk.Mainnet && (contract == FlowTransactionSchedulerUtils || contract == FlowTransactionScheduler) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove this considering the release is after mainnet deployment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will in the next PR

return "", fmt.Errorf("transaction scheduling is not yet supported on mainnet")
}

// Look up the contract address
// Handle system contracts using the systemcontracts library
if contract == FlowToken || contract == FungibleToken {
var flowGoChainID flowGo.ChainID
switch chainID {
case flowsdk.Emulator:
flowGoChainID = flowGo.Emulator
case flowsdk.Testnet:
flowGoChainID = flowGo.Testnet
case flowsdk.Mainnet:
flowGoChainID = flowGo.Mainnet
default:
return "", fmt.Errorf("unsupported chain ID: %s", chainID)
}

systemContracts := systemcontracts.SystemContractsForChain(flowGoChainID)
switch contract {
case FlowToken:
return systemContracts.FlowToken.Address.HexWithPrefix(), nil
case FungibleToken:
return systemContracts.FungibleToken.Address.HexWithPrefix(), nil
}
}

networkAddresses, contractExists := contractAddresses[contract]
if !contractExists {
return "", fmt.Errorf("unknown contract: %s", contract)
Expand Down
47 changes: 2 additions & 45 deletions internal/schedule/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func getRun(
networkStr := branding.GrayStyle.Render(globalFlags.Network)
txIDStr := branding.PurpleStyle.Render(transactionIDStr)

logger.Info("Getting scheduled transaction details")
logger.Info("Getting scheduled transaction details...")
logger.Info("")
logger.Info(fmt.Sprintf("🌐 Network: %s", networkStr))
logger.Info(fmt.Sprintf("🔍 Transaction ID: %s", txIDStr))
Expand Down Expand Up @@ -131,7 +131,6 @@ access(all) fun main(transactionID: UInt64): FlowTransactionScheduler.Transactio
return nil, fmt.Errorf("scheduled transaction not found")
}

// Log success
logger.Info("")
successIcon := branding.GreenStyle.Render("✅")
successMsg := branding.GreenStyle.Render("Transaction data retrieved successfully")
Expand All @@ -158,49 +157,7 @@ func (r *getResult) JSON() any {
}

func (r *getResult) String() string {
var output string

// ID
idLabel := branding.GrayStyle.Render(" ID:")
idValue := branding.PurpleStyle.Render(fmt.Sprintf("%d", r.data.ID))
output += fmt.Sprintf("%s %s\n", idLabel, idValue)

// Status
statusLabel := branding.GrayStyle.Render(" Status:")
statusValue := branding.GreenStyle.Render(GetStatusString(r.data.Status))
output += fmt.Sprintf("%s %s\n", statusLabel, statusValue)

// Priority
priorityLabel := branding.GrayStyle.Render(" Priority:")
priorityValue := branding.PurpleStyle.Render(GetPriorityString(r.data.Priority))
output += fmt.Sprintf("%s %s\n", priorityLabel, priorityValue)

// Execution Effort
effortLabel := branding.GrayStyle.Render(" Execution Effort:")
effortValue := branding.PurpleStyle.Render(fmt.Sprintf("%d", r.data.ExecutionEffort))
output += fmt.Sprintf("%s %s\n", effortLabel, effortValue)

// Fees
feesLabel := branding.GrayStyle.Render(" Fees:")
feesValue := branding.PurpleStyle.Render(fmt.Sprintf("%s FLOW", r.data.Fees))
output += fmt.Sprintf("%s %s\n", feesLabel, feesValue)

// Scheduled Timestamp
timestampLabel := branding.GrayStyle.Render(" Scheduled Timestamp:")
timestampValue := branding.PurpleStyle.Render(r.data.ScheduledTimestamp)
output += fmt.Sprintf("%s %s\n", timestampLabel, timestampValue)

// Handler Type
handlerTypeLabel := branding.GrayStyle.Render(" Handler Type:")
handlerTypeValue := branding.PurpleStyle.Render(r.data.HandlerTypeIdentifier)
output += fmt.Sprintf("%s %s\n", handlerTypeLabel, handlerTypeValue)

// Handler Address
handlerAddrLabel := branding.GrayStyle.Render(" Handler Address:")
handlerAddrValue := branding.PurpleStyle.Render(r.data.HandlerAddress)
output += fmt.Sprintf("%s %s\n", handlerAddrLabel, handlerAddrValue)

return output
return FormatTransactionDetails(r.data)
}

func (r *getResult) Oneliner() string {
Expand Down
Loading
Loading