From f256ad494adafa3e8ed0929c7833c7b0bdf85be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 17 Oct 2024 16:57:36 -0700 Subject: [PATCH] cherry-pick go crypto contract changes --- fvm/environment/contract_reader.go | 22 ++-- fvm/environment/facade_env.go | 5 +- fvm/evm/stdlib/checking.go | 98 +++++++++++++++++ fvm/evm/stdlib/contract.cdc | 119 +++++++++++++++++++++ fvm/evm/stdlib/type.go | 134 ++++++++++++++++++++++++ fvm/systemcontracts/system_contracts.go | 51 +++++---- 6 files changed, 401 insertions(+), 28 deletions(-) create mode 100644 fvm/evm/stdlib/checking.go create mode 100644 fvm/evm/stdlib/type.go diff --git a/fvm/environment/contract_reader.go b/fvm/environment/contract_reader.go index 2f21c4a9f92..720685c9f09 100644 --- a/fvm/environment/contract_reader.go +++ b/fvm/environment/contract_reader.go @@ -6,8 +6,10 @@ import ( "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/stdlib" "github.com/onflow/flow-go/fvm/errors" + "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/fvm/tracing" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/trace" @@ -15,21 +17,23 @@ import ( // ContractReader provide read access to contracts. type ContractReader struct { - tracer tracing.TracerSpan - meter Meter - - accounts Accounts + tracer tracing.TracerSpan + meter Meter + accounts Accounts + contracts *systemcontracts.SystemContracts } func NewContractReader( tracer tracing.TracerSpan, meter Meter, accounts Accounts, + contracts *systemcontracts.SystemContracts, ) *ContractReader { return &ContractReader{ - tracer: tracer, - meter: meter, - accounts: accounts, + tracer: tracer, + meter: meter, + accounts: accounts, + contracts: contracts, } } @@ -153,6 +157,10 @@ func (reader *ContractReader) GetCode( []byte, error, ) { + if location == stdlib.CryptoContractLocation { + location = reader.contracts.Crypto.Location() + } + contractLocation, ok := location.(common.AddressLocation) if !ok { return nil, errors.NewInvalidLocationErrorf( diff --git a/fvm/environment/facade_env.go b/fvm/environment/facade_env.go index 83f8b3cfd17..94ae07a3de3 100644 --- a/fvm/environment/facade_env.go +++ b/fvm/environment/facade_env.go @@ -9,6 +9,7 @@ import ( "github.com/onflow/flow-go/fvm/storage" "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/fvm/storage/state" + "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/fvm/tracing" ) @@ -61,8 +62,9 @@ func newFacadeEnvironment( accounts := NewAccounts(txnState) logger := NewProgramLogger(tracer, params.ProgramLoggerParams) runtime := NewRuntime(params.RuntimeParams) + chain := params.Chain systemContracts := NewSystemContracts( - params.Chain, + chain, tracer, logger, runtime) @@ -128,6 +130,7 @@ func newFacadeEnvironment( tracer, meter, accounts, + systemcontracts.SystemContractsForChain(chain.ChainID()), ), ContractUpdater: NoContractUpdater{}, Programs: NewPrograms( diff --git a/fvm/evm/stdlib/checking.go b/fvm/evm/stdlib/checking.go new file mode 100644 index 00000000000..07e48087da5 --- /dev/null +++ b/fvm/evm/stdlib/checking.go @@ -0,0 +1,98 @@ +package stdlib + +import ( + "fmt" + + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" +) + +// checkingInterface is a runtime.Interface implementation +// that can be used for ParseAndCheckProgram. +// It is not suitable for execution. +type checkingInterface struct { + runtime.EmptyRuntimeInterface + SystemContractCodes map[common.Location][]byte + Programs map[runtime.Location]*interpreter.Program +} + +var _ runtime.Interface = &checkingInterface{} + +func (*checkingInterface) ResolveLocation( + identifiers []runtime.Identifier, + location runtime.Location, +) ( + []runtime.ResolvedLocation, + error, +) { + + addressLocation, isAddress := location.(common.AddressLocation) + + // if the location is not an address location, e.g. an identifier location + // (`import Crypto`), then return a single resolved location which declares + // all identifiers. + if !isAddress { + return []runtime.ResolvedLocation{ + { + Location: location, + Identifiers: identifiers, + }, + }, nil + } + + if len(identifiers) == 0 { + return nil, fmt.Errorf("no identifiers provided") + } + + // return one resolved location per identifier. + // each resolved location is an address contract location + resolvedLocations := make([]runtime.ResolvedLocation, len(identifiers)) + for i := range resolvedLocations { + identifier := identifiers[i] + resolvedLocations[i] = runtime.ResolvedLocation{ + Location: common.AddressLocation{ + Address: addressLocation.Address, + Name: identifier.Identifier, + }, + Identifiers: []runtime.Identifier{identifier}, + } + } + + return resolvedLocations, nil +} + +func (r *checkingInterface) GetOrLoadProgram( + location runtime.Location, + load func() (*interpreter.Program, error), +) ( + program *interpreter.Program, + err error, +) { + if r.Programs == nil { + r.Programs = map[runtime.Location]*interpreter.Program{} + } + + var ok bool + program, ok = r.Programs[location] + if ok { + return + } + + program, err = load() + + // NOTE: important: still set empty program, + // even if error occurred + + r.Programs[location] = program + + return +} + +func (r *checkingInterface) GetCode(location common.Location) ([]byte, error) { + return r.SystemContractCodes[location], nil +} + +func (r *checkingInterface) GetAccountContractCode(location common.AddressLocation) (code []byte, err error) { + return r.SystemContractCodes[location], nil +} diff --git a/fvm/evm/stdlib/contract.cdc b/fvm/evm/stdlib/contract.cdc index fa383ee9bdc..7c1781bd312 100644 --- a/fvm/evm/stdlib/contract.cdc +++ b/fvm/evm/stdlib/contract.cdc @@ -4,8 +4,127 @@ import "FlowToken" access(all) contract EVM { +<<<<<<< HEAD access(all) event CadenceOwnedAccountCreated(addressBytes: [UInt8; 20]) +======= + // Entitlements enabling finer-grained access control on a CadenceOwnedAccount + access(all) entitlement Validate + access(all) entitlement Withdraw + access(all) entitlement Call + access(all) entitlement Deploy + access(all) entitlement Owner + access(all) entitlement Bridge + + /// Block executed event is emitted when a new block is created, + /// which always happens when a transaction is executed. + access(all) + event BlockExecuted( + // height or number of the block + height: UInt64, + // hash of the block + hash: [UInt8; 32], + // timestamp of the block creation + timestamp: UInt64, + // total Flow supply + totalSupply: Int, + // all gas used in the block by transactions included + totalGasUsed: UInt64, + // parent block hash + parentHash: [UInt8; 32], + // root hash of all the transaction receipts + receiptRoot: [UInt8; 32], + // root hash of all the transaction hashes + transactionHashRoot: [UInt8; 32], + /// value returned for PREVRANDAO opcode + prevrandao: [UInt8; 32], + ) + + /// Transaction executed event is emitted every time a transaction + /// is executed by the EVM (even if failed). + access(all) + event TransactionExecuted( + // hash of the transaction + hash: [UInt8; 32], + // index of the transaction in a block + index: UInt16, + // type of the transaction + type: UInt8, + // RLP encoded transaction payload + payload: [UInt8], + // code indicating a specific validation (201-300) or execution (301-400) error + errorCode: UInt16, + // a human-readable message about the error (if any) + errorMessage: String, + // the amount of gas transaction used + gasConsumed: UInt64, + // if transaction was a deployment contains a newly deployed contract address + contractAddress: String, + // RLP encoded logs + logs: [UInt8], + // block height in which transaction was included + blockHeight: UInt64, + /// captures the hex encoded data that is returned from + /// the evm. For contract deployments + /// it returns the code deployed to + /// the address provided in the contractAddress field. + /// in case of revert, the smart contract custom error message + /// is also returned here (see EIP-140 for more details). + returnedData: [UInt8], + /// captures the input and output of the calls (rlp encoded) to the extra + /// precompiled contracts (e.g. Cadence Arch) during the transaction execution. + /// This data helps to replay the transactions without the need to + /// have access to the full cadence state data. + precompiledCalls: [UInt8], + /// stateUpdateChecksum provides a mean to validate + /// the updates to the storage when re-executing a transaction off-chain. + stateUpdateChecksum: [UInt8; 4] + ) + + access(all) + event CadenceOwnedAccountCreated(address: String) + + /// FLOWTokensDeposited is emitted when FLOW tokens is bridged + /// into the EVM environment. Note that this event is not emitted + /// for transfer of flow tokens between two EVM addresses. + /// Similar to the FungibleToken.Deposited event + /// this event includes a depositedUUID that captures the + /// uuid of the source vault. + access(all) + event FLOWTokensDeposited( + address: String, + amount: UFix64, + depositedUUID: UInt64, + balanceAfterInAttoFlow: UInt + ) + + /// FLOWTokensWithdrawn is emitted when FLOW tokens are bridged + /// out of the EVM environment. Note that this event is not emitted + /// for transfer of flow tokens between two EVM addresses. + /// similar to the FungibleToken.Withdrawn events + /// this event includes a withdrawnUUID that captures the + /// uuid of the returning vault. + access(all) + event FLOWTokensWithdrawn( + address: String, + amount: UFix64, + withdrawnUUID: UInt64, + balanceAfterInAttoFlow: UInt + ) + + /// BridgeAccessorUpdated is emitted when the BridgeAccessor Capability + /// is updated in the stored BridgeRouter along with identifying + /// information about both. + access(all) + event BridgeAccessorUpdated( + routerType: Type, + routerUUID: UInt64, + routerAddress: Address, + accessorType: Type, + accessorUUID: UInt64, + accessorAddress: Address + ) +>>>>>>> dd533f6ece (translate Crypto identifier location to chain-specific Crypto contract) /// EVMAddress is an EVM-compatible address access(all) diff --git a/fvm/evm/stdlib/type.go b/fvm/evm/stdlib/type.go new file mode 100644 index 00000000000..b2368615e10 --- /dev/null +++ b/fvm/evm/stdlib/type.go @@ -0,0 +1,134 @@ +package stdlib + +import ( + "fmt" + + "github.com/onflow/cadence" + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" + coreContracts "github.com/onflow/flow-core-contracts/lib/go/contracts" + + "github.com/onflow/flow-go/fvm/systemcontracts" + "github.com/onflow/flow-go/model/flow" +) + +func newContractType(chainID flow.ChainID) *sema.CompositeType { + + contracts := systemcontracts.SystemContractsForChain(chainID) + + evmCode := ContractCode( + contracts.NonFungibleToken.Address, + contracts.FungibleToken.Address, + contracts.FlowToken.Address, + ) + + evmContractAddress := contracts.EVMContract.Address + + evmContractLocation := common.AddressLocation{ + Address: common.Address(evmContractAddress), + Name: ContractName, + } + + templatesEnv := contracts.AsTemplateEnv() + + runtimeInterface := &checkingInterface{ + SystemContractCodes: map[common.Location][]byte{ + contracts.ViewResolver.Location(): coreContracts.ViewResolver(), + contracts.Burner.Location(): coreContracts.Burner(), + contracts.FungibleToken.Location(): coreContracts.FungibleToken(templatesEnv), + contracts.NonFungibleToken.Location(): coreContracts.NonFungibleToken(templatesEnv), + contracts.MetadataViews.Location(): coreContracts.MetadataViews(templatesEnv), + contracts.FlowToken.Location(): coreContracts.FlowToken(templatesEnv), + contracts.FungibleTokenMetadataViews.Location(): coreContracts.FungibleTokenMetadataViews(templatesEnv), + stdlib.CryptoContractLocation: coreContracts.Crypto(), + }, + } + + env := runtime.NewBaseInterpreterEnvironment(runtime.Config{}) + env.Configure( + runtimeInterface, + runtime.NewCodesAndPrograms(), + nil, + nil, + ) + + SetupEnvironment(env, nil, evmContractAddress) + + program, err := env.ParseAndCheckProgram(evmCode, evmContractLocation, false) + if err != nil { + panic(err) + } + + evmContractTypeID := evmContractLocation.TypeID(nil, ContractName) + + return program.Elaboration.CompositeType(evmContractTypeID) +} + +var contractTypes = map[flow.ChainID]*sema.CompositeType{} + +type CadenceTypes struct { + TransactionExecuted *cadence.EventType + BlockExecuted *cadence.EventType +} + +var cadenceTypes = map[flow.ChainID]CadenceTypes{} + +func exportCadenceEventType(contractType *sema.CompositeType, name string) (*cadence.EventType, error) { + transactionEventType, ok := contractType.GetNestedTypes().Get(name) + if !ok { + return nil, fmt.Errorf("missing %s type", name) + } + exportedType := runtime.ExportType( + transactionEventType, + map[sema.TypeID]cadence.Type{}, + ) + + eventType, ok := exportedType.(*cadence.EventType) + if !ok { + return nil, fmt.Errorf("type %s is not an event", name) + } + + return eventType, nil +} + +func init() { + for _, chain := range flow.AllChainIDs() { + contractType := newContractType(chain) + contractTypes[chain] = contractType + + transactionExecutedEvent, err := exportCadenceEventType(contractType, "TransactionExecuted") + if err != nil { + panic(err) + } + + blockExecutedEvent, err := exportCadenceEventType(contractType, "BlockExecuted") + if err != nil { + panic(err) + } + + cadenceTypes[chain] = CadenceTypes{ + TransactionExecuted: transactionExecutedEvent, + BlockExecuted: blockExecutedEvent, + } + } +} + +func ContractTypeForChain(chainID flow.ChainID) *sema.CompositeType { + contractType, ok := contractTypes[chainID] + if !ok { + // this is a panic, since it can only happen if the code is wrong + panic(fmt.Sprintf("unknown chain: %s", chainID)) + } + return contractType +} + +func CadenceTypesForChain(chainID flow.ChainID) CadenceTypes { + cadenceTypes, ok := cadenceTypes[chainID] + if !ok { + // this is a panic, since it can only happen if the code is wrong + panic(fmt.Sprintf("unknown chain: %s", chainID)) + } + return cadenceTypes +} diff --git a/fvm/systemcontracts/system_contracts.go b/fvm/systemcontracts/system_contracts.go index a0c3a39848d..8ab155c3aae 100644 --- a/fvm/systemcontracts/system_contracts.go +++ b/fvm/systemcontracts/system_contracts.go @@ -24,21 +24,25 @@ import ( const ( // Unqualified names of system smart contracts (not including address prefix) - ContractNameEpoch = "FlowEpoch" - ContractNameIDTableStaking = "FlowIDTableStaking" - ContractNameClusterQC = "FlowClusterQC" - ContractNameDKG = "FlowDKG" - ContractNameServiceAccount = "FlowServiceAccount" - ContractNameFlowFees = "FlowFees" - ContractNameStorageFees = "FlowStorageFees" - ContractNameNodeVersionBeacon = "NodeVersionBeacon" - ContractNameRandomBeaconHistory = "RandomBeaconHistory" - ContractNameFungibleToken = "FungibleToken" - ContractNameFlowToken = "FlowToken" - ContractNameNonFungibleToken = "NonFungibleToken" - ContractNameMetadataViews = "MetadataViews" - ContractNameViewResolver = "ViewResolver" - ContractNameEVM = "EVM" + ContractNameEpoch = "FlowEpoch" + ContractNameIDTableStaking = "FlowIDTableStaking" + ContractNameClusterQC = "FlowClusterQC" + ContractNameDKG = "FlowDKG" + ContractNameServiceAccount = "FlowServiceAccount" + ContractNameFlowFees = "FlowFees" + ContractNameStorageFees = "FlowStorageFees" + ContractNameNodeVersionBeacon = "NodeVersionBeacon" + ContractNameRandomBeaconHistory = "RandomBeaconHistory" + ContractNameFungibleToken = "FungibleToken" + ContractNameFlowToken = "FlowToken" + ContractNameFungibleTokenSwitchboard = "FungibleTokenSwitchboard" + ContractNameFungibleTokenMetadataViews = "FungibleTokenMetadataViews" + ContractNameNonFungibleToken = "NonFungibleToken" + ContractNameMetadataViews = "MetadataViews" + ContractNameViewResolver = "ViewResolver" + ContractNameEVM = "EVM" + ContractNameBurner = "Burner" + ContractNameCrypto = "Crypto" // AccountNameEVMStorage is not a contract, but a special account that is used to store EVM state AccountNameEVMStorage = "EVMStorageAccount" @@ -147,6 +151,10 @@ type SystemContracts struct { // EVM related contracts EVMContract SystemContract EVMStorage SystemAccount + + // Utility contracts + Burner SystemContract + Crypto SystemContract } // AsTemplateEnv returns a template environment with all system contracts filled in. @@ -170,11 +178,8 @@ func (c SystemContracts) AsTemplateEnv() templates.Environment { // The following contracts dont exist on the template env yet // that is not a problem, but they are still listed here for completeness. - // NonFungibleToken: c.NonFungibleToken.Address.Hex(), - // MetadataViews : c.MetadataViews.Address.Hex(), - // ViewResolver : c.ViewResolver.Address.Hex(), - - // EVMAddress: c.EVM.Address.Hex(), + BurnerAddress: c.Burner.Address.Hex(), + CryptoAddress: c.Crypto.Address.Hex(), } } @@ -321,6 +326,9 @@ func init() { ContractNameEVM: serviceAddressFunc, AccountNameEVMStorage: evmStorageEVMFunc, + + ContractNameBurner: burnerAddressFunc, + ContractNameCrypto: serviceAddressFunc, } getSystemContractsForChain := func(chainID flow.ChainID) *SystemContracts { @@ -372,6 +380,9 @@ func init() { EVMContract: addressOfContract(ContractNameEVM), EVMStorage: addressOfAccount(AccountNameEVMStorage), + + Burner: addressOfContract(ContractNameBurner), + Crypto: addressOfContract(ContractNameCrypto), } return contracts