Skip to content

Commit

Permalink
Merge pull request #5677 from onflow/giovanni/add-pre-1-bridge-interface
Browse files Browse the repository at this point in the history
[EVM] Add bridging interface to EVM contract
  • Loading branch information
sisyphusSmiling authored Apr 19, 2024
2 parents b27fd5d + ce6f3ce commit 41926ad
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 20 deletions.
6 changes: 5 additions & 1 deletion cmd/util/ledger/migrations/change_contract_code_migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,11 @@ func SystemContractChanges(chainID flow.ChainID) []SystemContractChange {
// EVM related contracts
NewSystemContractChange(
systemContracts.EVMContract,
evm.ContractCode(systemContracts.FlowToken.Address),
evm.ContractCode(
systemContracts.NonFungibleToken.Address,
systemContracts.FungibleToken.Address,
systemContracts.FlowToken.Address,
),
),
}
}
Expand Down
6 changes: 3 additions & 3 deletions fvm/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ func (b *bootstrapExecutor) Execute() error {
b.setStakingAllowlist(service, b.identities.NodeIDs())

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

return nil
}
Expand Down Expand Up @@ -806,7 +806,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 @@ -817,7 +817,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
130 changes: 130 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 @@ -19,6 +21,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 @@ -288,6 +303,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: &{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(all)
fun withdrawNFT(
type: Type,
id: UInt256,
feeProvider: &{FungibleToken.Provider}
): @NonFungibleToken.NFT {
return <- EVM.borrowBridgeAccessor().withdrawNFT(
caller: &self as &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: &{FungibleToken.Provider}
) {
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(all)
fun withdrawTokens(
type: Type,
amount: UInt256,
feeProvider: &{FungibleToken.Provider}
): @FungibleToken.Vault {
return <- EVM.borrowBridgeAccessor().withdrawTokens(
caller: &self as &CadenceOwnedAccount,
type: type,
amount: amount,
feeProvider: feeProvider
)
}
}

/// Creates a new cadence owned account
Expand Down Expand Up @@ -492,4 +560,66 @@ 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(all)
fun depositNFT(
nft: @NonFungibleToken.NFT,
to: EVMAddress,
feeProvider: &{FungibleToken.Provider}
)

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

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

/// Endpoint enabling the bridging of fungible tokens from EVM
access(all)
fun withdrawTokens(
caller: &CadenceOwnedAccount,
type: Type,
amount: UInt256,
feeProvider: &{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(all) view fun borrowBridgeAccessor(): &{BridgeAccessor}

/// Sets the BridgeAccessor Capability in the BridgeRouter
access(all) fun setBridgeAccessor(_ accessor: Capability<&{BridgeAccessor}>) {
pre {
accessor.check(): "Invalid BridgeAccessor Capability provided"
}
}
}

/// Returns a reference to the BridgeAccessor designated for internal bridge requests
access(self)
view fun borrowBridgeAccessor(): &{BridgeAccessor} {
return self.account.borrow<&{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 @@ -223,7 +223,7 @@ func deployContracts(
},
{
name: stdlib.ContractName,
code: stdlib.ContractCode(contractsAddress),
code: stdlib.ContractCode(contractsAddress, contractsAddress, contractsAddress),
},
}

Expand Down
16 changes: 5 additions & 11 deletions fvm/fvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3071,6 +3071,9 @@ func TestEVM(t *testing.T) {
).Return(block1.Header, nil)

ctxOpts := []fvm.Option{
// 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.WithEVMEnabled(true),
fvm.WithBlocks(blocks),
Expand Down Expand Up @@ -3226,17 +3229,8 @@ func TestEVM(t *testing.T) {
)

t.Run("deploy contract code", newVMTest().
withBootstrapProcedureOptions(
fvm.WithSetupEVMEnabled(true),
).
withContextOptions(
// 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.WithBlocks(blocks),
fvm.WithBlockHeader(block1.Header),
).
withBootstrapProcedureOptions(fvm.WithSetupEVMEnabled(true)).
withContextOptions(ctxOpts...).
run(func(
t *testing.T,
vm fvm.VM,
Expand Down

0 comments on commit 41926ad

Please sign in to comment.