Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EVM] Add bridging interface to EVM contract - stable cadence port #5716

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f45b0d4
add bridging interface to COA
sisyphusSmiling Apr 12, 2024
eea11a0
fix updated EVM dependency aliasing
sisyphusSmiling Apr 15, 2024
4f5de06
update EVM bridging interfaces for Cadence 0.42
sisyphusSmiling Apr 15, 2024
0f00313
remove entitlement from EVM & fix bootstrap
sisyphusSmiling Apr 16, 2024
b37e4c3
fix casting statement in EVM contract
sisyphusSmiling Apr 16, 2024
d07704a
fix accepted NFT & Vault conformance types in EVM contract
sisyphusSmiling Apr 16, 2024
46b39cf
fix change_contract_code_migration
sisyphusSmiling Apr 16, 2024
8e04ecb
commit fvm_test.go patch thanks to @m-peter
sisyphusSmiling Apr 16, 2024
9d0eb93
fix stdlib.ContractCode var mutation scope pointed out by @m-peter
sisyphusSmiling Apr 16, 2024
f55c14b
add BridgeAccessorUpdated event & BridgeAccessor setter to EVM contract
sisyphusSmiling Apr 17, 2024
37ee20c
fix failing fvm tests
sisyphusSmiling Apr 17, 2024
f3b72b2
cherry-pick fix
janezpodhostnik Apr 18, 2024
f617988
update EVM contract with 1.0 bridging interface
sisyphusSmiling Apr 18, 2024
1777bef
update state commitments
sisyphusSmiling Apr 18, 2024
27d9116
fix execution tests with updated state commitments
sisyphusSmiling Apr 18, 2024
6e45ab5
Merge branch 'feature/stable-cadence' into janez/add-pre-1-bridge-int…
sisyphusSmiling Apr 19, 2024
d2ab8e0
Merge branch 'feature/stable-cadence' into janez/add-pre-1-bridge-int…
janezpodhostnik Apr 24, 2024
273ddca
Merge branch 'feature/stable-cadence' into janez/add-pre-1-bridge-int…
janezpodhostnik Apr 26, 2024
ae64dbc
fix test
janezpodhostnik Apr 26, 2024
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
2 changes: 2 additions & 0 deletions cmd/util/ledger/migrations/change_contract_code_migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractsMigratio
NewSystemContractChange(
systemContracts.EVMContract,
evm.ContractCode(
flow.HexToAddress(env.NonFungibleTokenAddress),
flow.HexToAddress(env.FungibleTokenAddress),
flow.HexToAddress(env.FlowTokenAddress),
),
),
Expand Down
2 changes: 1 addition & 1 deletion engine/execution/state/bootstrap/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestBootstrapLedger(t *testing.T) {
}

func TestBootstrapLedger_ZeroTokenSupply(t *testing.T) {
expectedStateCommitmentBytes, _ := hex.DecodeString("42aae9eb1f9238cac7b0cdb01515fa6df78435ef59c470484a63b74b8eb68911")
expectedStateCommitmentBytes, _ := hex.DecodeString("6815248ba64e2a37bb8674108c83c97a04c12acb3e44ce585d3aab15af0b9d05")
expectedStateCommitment, err := flow.ToStateCommitment(expectedStateCommitmentBytes)
require.NoError(t, err)

Expand Down
6 changes: 3 additions & 3 deletions fvm/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ func (b *bootstrapExecutor) Execute() error {
b.deployStakingCollection(service, &env)

// sets up the EVM environment
b.setupEVM(service, fungibleToken, flowToken)
b.setupEVM(service, nonFungibleToken, fungibleToken, flowToken)

err = expectAccounts(systemcontracts.EVMStorageAccountIndex)
if err != nil {
Expand Down Expand Up @@ -895,7 +895,7 @@ func (b *bootstrapExecutor) setStakingAllowlist(
panicOnMetaInvokeErrf("failed to set staking allow-list: %s", txError, err)
}

func (b *bootstrapExecutor) setupEVM(serviceAddress, fungibleTokenAddress, flowTokenAddress flow.Address) {
func (b *bootstrapExecutor) setupEVM(serviceAddress, nonFungibleTokenAddress, fungibleTokenAddress, flowTokenAddress flow.Address) {
if b.setupEVMEnabled {
// account for storage
// we dont need to deploy anything to this account, but it needs to exist
Expand All @@ -906,7 +906,7 @@ func (b *bootstrapExecutor) setupEVM(serviceAddress, fungibleTokenAddress, flowT
// deploy the EVM contract to the service account
tx := blueprints.DeployContractTransaction(
serviceAddress,
stdlib.ContractCode(flowTokenAddress),
stdlib.ContractCode(nonFungibleTokenAddress, fungibleTokenAddress, flowTokenAddress),
stdlib.ContractName,
)
// WithEVMEnabled should only be used after we create an account for storage
Expand Down
139 changes: 139 additions & 0 deletions fvm/evm/stdlib/contract.cdc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Crypto
import "NonFungibleToken"
import "FungibleToken"
import "FlowToken"

access(all)
Expand All @@ -10,6 +12,7 @@ contract EVM {
access(all) entitlement Call
access(all) entitlement Deploy
access(all) entitlement Owner
access(all) entitlement Bridge

access(all)
event CadenceOwnedAccountCreated(addressBytes: [UInt8; 20])
Expand All @@ -26,6 +29,19 @@ contract EVM {
access(all)
event FLOWTokensWithdrawn(addressBytes: [UInt8; 20], amount: UFix64)

/// 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
)

/// EVMAddress is an EVM-compatible address
access(all)
struct EVMAddress {
Expand Down Expand Up @@ -301,6 +317,59 @@ contract EVM {
value: value.attoflow
) as! Result
}

/// Bridges the given NFT to the EVM environment, requiring a Provider from which to withdraw a fee to fulfill
/// the bridge request
access(all)
fun depositNFT(
nft: @{NonFungibleToken.NFT},
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
) {
EVM.borrowBridgeAccessor().depositNFT(nft: <-nft, to: self.address(), feeProvider: feeProvider)
}

/// Bridges the given NFT from the EVM environment, requiring a Provider from which to withdraw a fee to fulfill
/// the bridge request. Note: the caller should own the requested NFT in EVM
access(Owner | Bridge)
fun withdrawNFT(
type: Type,
id: UInt256,
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
): @{NonFungibleToken.NFT} {
return <- EVM.borrowBridgeAccessor().withdrawNFT(
caller: &self as auth(Call) &CadenceOwnedAccount,
type: type,
id: id,
feeProvider: feeProvider
)
}

/// Bridges the given Vault to the EVM environment, requiring a Provider from which to withdraw a fee to fulfill
/// the bridge request
access(all)
fun depositTokens(
vault: @{FungibleToken.Vault},
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
) {
EVM.borrowBridgeAccessor().depositTokens(vault: <-vault, to: self.address(), feeProvider: feeProvider)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
EVM.borrowBridgeAccessor().depositTokens(vault: <-vault, to: self.address(), feeProvider: feeProvider)
EVM.borrowBridgeAccessor().depositTokens(
vault: <-vault,
to: self.address(),
feeProvider: feeProvider
)

}

/// Bridges the given fungible tokens from the EVM environment, requiring a Provider from which to withdraw a
/// fee to fulfill the bridge request. Note: the caller should own the requested tokens & sufficient balance of
/// requested tokens in EVM
access(Owner | Bridge)
fun withdrawTokens(
type: Type,
amount: UInt256,
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
): @{FungibleToken.Vault} {
return <- EVM.borrowBridgeAccessor().withdrawTokens(
caller: &self as auth(Call) &CadenceOwnedAccount,
type: type,
amount: amount,
feeProvider: feeProvider
)
}
}

/// Creates a new cadence owned account
Expand Down Expand Up @@ -500,4 +569,74 @@ contract EVM {
fun getLatestBlock(): EVMBlock {
return InternalEVM.getLatestBlock() as! EVMBlock
}

/// Interface for a resource which acts as an entrypoint to the VM bridge
access(all)
resource interface BridgeAccessor {

/// Endpoint enabling the bridging of an NFT to EVM
access(Bridge)
fun depositNFT(
nft: @{NonFungibleToken.NFT},
to: EVMAddress,
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
)

/// Endpoint enabling the bridging of an NFT from EVM
access(Bridge)
fun withdrawNFT(
caller: auth(Call) &CadenceOwnedAccount,
type: Type,
id: UInt256,
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
): @{NonFungibleToken.NFT}

/// Endpoint enabling the bridging of a fungible token vault to EVM
access(Bridge)
fun depositTokens(
vault: @{FungibleToken.Vault},
to: EVMAddress,
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
)

/// Endpoint enabling the bridging of fungible tokens from EVM
access(Bridge)
fun withdrawTokens(
caller: auth(Call) &CadenceOwnedAccount,
type: Type,
amount: UInt256,
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
): @{FungibleToken.Vault}
}

/// Interface which captures a Capability to the bridge Accessor, saving it within the BridgeRouter resource
access(all)
resource interface BridgeRouter {

/// Returns a reference to the BridgeAccessor designated for internal bridge requests
access(Bridge) view fun borrowBridgeAccessor(): auth(Bridge) &{BridgeAccessor}

/// Sets the BridgeAccessor Capability in the BridgeRouter
access(Bridge) fun setBridgeAccessor(_ accessor: Capability<auth(Bridge) &{BridgeAccessor}>) {
pre {
accessor.check(): "Invalid BridgeAccessor Capability provided"
emit BridgeAccessorUpdated(
routerType: self.getType(),
routerUUID: self.uuid,
routerAddress: self.owner?.address ?? panic("Router must have an owner to be identified"),
accessorType: accessor.borrow()!.getType(),
accessorUUID: accessor.borrow()!.uuid,
accessorAddress: accessor.address
)
}
}
}

/// Returns a reference to the BridgeAccessor designated for internal bridge requests
access(self)
view fun borrowBridgeAccessor(): auth(Bridge) &{BridgeAccessor} {
return self.account.storage.borrow<auth(Bridge) &{BridgeRouter}>(from: /storage/evmBridgeRouter)
?.borrowBridgeAccessor()
?? panic("Could not borrow reference to the EVM bridge")
}
}
19 changes: 15 additions & 4 deletions fvm/evm/stdlib/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,24 @@ import (
//go:embed contract.cdc
var contractCode string

var flowTokenImportPattern = regexp.MustCompile(`(?m)^import "FlowToken"\n`)
var nftImportPattern = regexp.MustCompile(`(?m)^import "NonFungibleToken"`)
var fungibleTokenImportPattern = regexp.MustCompile(`(?m)^import "FungibleToken"`)
var flowTokenImportPattern = regexp.MustCompile(`(?m)^import "FlowToken"`)

func ContractCode(flowTokenAddress flow.Address) []byte {
return []byte(flowTokenImportPattern.ReplaceAllString(
func ContractCode(nonFungibleTokenAddress, fungibleTokenAddress, flowTokenAddress flow.Address) []byte {
evmContract := nftImportPattern.ReplaceAllString(
contractCode,
fmt.Sprintf("import NonFungibleToken from %s", nonFungibleTokenAddress.HexWithPrefix()),
)
evmContract = fungibleTokenImportPattern.ReplaceAllString(
evmContract,
fmt.Sprintf("import FungibleToken from %s", fungibleTokenAddress.HexWithPrefix()),
)
evmContract = flowTokenImportPattern.ReplaceAllString(
evmContract,
fmt.Sprintf("import FlowToken from %s", flowTokenAddress.HexWithPrefix()),
))
)
return []byte(evmContract)
}

const ContractName = "EVM"
Expand Down
2 changes: 1 addition & 1 deletion fvm/evm/stdlib/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ func deployContracts(
},
{
name: stdlib.ContractName,
code: stdlib.ContractCode(contractsAddress),
code: stdlib.ContractCode(contractsAddress, contractsAddress, contractsAddress),
},
}

Expand Down
6 changes: 6 additions & 0 deletions fvm/fvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2835,6 +2835,10 @@ func TestEVM(t *testing.T) {
t.Run("successful transaction", newVMTest().
withContextOptions(
fvm.WithEVMEnabled(true),
// default is testnet, but testnet has a special EVM storage contract location
// so we have to use emulator here so that the EVM storage contract is deployed
// to the 5th address
fvm.WithChain(flow.Emulator.Chain()),
fvm.WithCadenceLogging(true),
).
run(func(
Expand Down Expand Up @@ -2988,7 +2992,9 @@ func TestEVM(t *testing.T) {
)

t.Run("deploy contract code", newVMTest().
withBootstrapProcedureOptions(fvm.WithSetupEVMEnabled(true)).
withContextOptions(
fvm.WithEVMEnabled(true),
// default is testnet, but testnet has a special EVM storage contract location
// so we have to use emulator here so that the EVM storage contract is deployed
// to the 5th address
Expand Down
6 changes: 3 additions & 3 deletions utils/unittest/execution_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const ServiceAccountPrivateKeySignAlgo = crypto.ECDSAP256
const ServiceAccountPrivateKeyHashAlgo = hash.SHA2_256

// Pre-calculated state commitment with root account with the above private key
const GenesisStateCommitmentHex = "5d2ea7f6fe9754d7dcf3f649d1cb8ab3e4aa02f9ed51d88826dd49399c4d12f1"
const GenesisStateCommitmentHex = "8bf21dc9fbef2b74c2b5a7147bdd51358eb97c618c19903ed9c9bf58bc4e7d23"

var GenesisStateCommitment flow.StateCommitment

Expand Down Expand Up @@ -87,10 +87,10 @@ func genesisCommitHexByChainID(chainID flow.ChainID) string {
return GenesisStateCommitmentHex
}
if chainID == flow.Testnet {
return "76ab2529c0ca1d67790f4d3cd4834a3d8441492d90c4b06de3ab920b02ac25f9"
return "87f26f515391aac9dca250b614bf85cf0f64d3d1d0626b36d070132af8e188d8"
}
if chainID == flow.Sandboxnet {
return "e1c08b17f9e5896f03fe28dd37ca396c19b26628161506924fbf785834646ea1"
}
return "fae892769c66db9c7678fca9ccf33f794abae2bdcff7a6e0b1a04cd72562e61b"
return "9e0b468135e41c50809f6d010e92ad4642fd84799a0915e64b6d8772ec77a49b"
}
Loading