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

Add Cadence transaction batching EVM calls #12

Merged
merged 24 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
761c724
forge install: random-coin-toss
sisyphusSmiling Oct 31, 2024
9f2a8de
remove submodule
sisyphusSmiling Oct 31, 2024
f0453d2
forge install: random-coin-toss
sisyphusSmiling Oct 31, 2024
8f6e417
update foundry.toml
sisyphusSmiling Oct 31, 2024
a37c417
update foundry remappings
sisyphusSmiling Oct 31, 2024
161e07b
add CadenceArchUtils library & test coverage on random mint
sisyphusSmiling Nov 1, 2024
5976256
update dependencies
sisyphusSmiling Nov 1, 2024
c73ef70
replace local dep with foundry installed dep
sisyphusSmiling Nov 1, 2024
aa01cbb
add custom errors to ERC721
sisyphusSmiling Nov 4, 2024
6ded160
add bundled Cadence txn
sisyphusSmiling Nov 4, 2024
54b4550
add stepwise Cadence txns
sisyphusSmiling Nov 4, 2024
26f071e
remove unused GitHub cadence tests workflow
sisyphusSmiling Nov 4, 2024
71206e8
remove WETH9 contract
sisyphusSmiling Nov 5, 2024
9dba454
update transaction comments
sisyphusSmiling Nov 5, 2024
ee20308
update README with testnet deployments
sisyphusSmiling Nov 5, 2024
0975463
remove onflow/random-coin-toss
sisyphusSmiling Nov 7, 2024
f5fa952
add Cadence tests & helper solidity contract
sisyphusSmiling Nov 7, 2024
bffd632
forge install: flow-sol-utils
sisyphusSmiling Nov 7, 2024
c4b6971
update solidity dependencies to use flow-sol-utils
sisyphusSmiling Nov 7, 2024
20fe203
Merge branch 'gio/add-random-mint' into gio/add-cadence-test
sisyphusSmiling Nov 7, 2024
029bde7
Merge branch 'gio/add-random-mint' into gio/add-batched-calls
sisyphusSmiling Nov 7, 2024
bbecea4
Merge branch 'gio/add-batched-calls' into gio/add-cadence-test
sisyphusSmiling Nov 7, 2024
d203cc9
fix failing Cadence tests & remove debug tests
sisyphusSmiling Nov 7, 2024
7856847
Merge branch 'main' into gio/add-batched-calls
sisyphusSmiling Nov 7, 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
30 changes: 0 additions & 30 deletions .github/workflows/cadence_test.yml

This file was deleted.

13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
### Batched Cadence EVM Execution Example
# Batched Cadence EVM Execution Example

> This repo contains an example of how to batch EVM execution on Flow using Cadence.

:building_construction: WIP
:building_construction: Currently work in progress.

## Deployments

The relevant contracts can be found at the following addresses on Flow Testnet:

|Contract|Address|
|---|---|
|`MaybeMintERC72`|[`0xdbC43Ba45381e02825b14322cDdd15eC4B3164E6`](https://evm-testnet.flowscan.io/address/0xdbc43ba45381e02825b14322cddd15ec4b3164e6?tab=contract_code)|
|`WFLOW`|[`0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e`](https://evm-testnet.flowscan.io/token/0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e?tab=contract_code)|
38 changes: 38 additions & 0 deletions cadence/tests/test_helpers.cdc

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions cadence/tests/transactions/create_coa.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import "EVM"
import "FungibleToken"
import "FlowToken"

/// Configures a COA in the signer's Flow account, funding with the specified amount. If the COA already exists, the
/// transaction reverts.
///
transaction(amount: UFix64) {
let coa: &EVM.CadenceOwnedAccount
let sentVault: @FlowToken.Vault

prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue, UnpublishCapability) &Account) {
let storagePath = /storage/evm
let publicPath = /public/evm

// Revert if the CadenceOwnedAccount already exists
if signer.storage.type(at: storagePath) != nil {
panic("Storage collision - Resource already stored at path=".concat(storagePath.toString()))
}

// Configure the CadenceOwnedAccount
signer.storage.save<@EVM.CadenceOwnedAccount>(<-EVM.createCadenceOwnedAccount(), to: storagePath)
let addressableCap = signer.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(storagePath)
signer.capabilities.unpublish(publicPath)
signer.capabilities.publish(addressableCap, at: publicPath)

// Reference the CadeceOwnedAccount
self.coa = signer.storage.borrow<auth(EVM.Owner) &EVM.CadenceOwnedAccount>(from: /storage/evm)
?? panic("Missing or mis-typed CadenceOwnedAccount at /storage/evm")

// Withdraw the amount from the signer's FlowToken vault
let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(
from: /storage/flowTokenVault
) ?? panic("Could not borrow reference to the owner's Vault!")
self.sentVault <- vaultRef.withdraw(amount: amount) as! @FlowToken.Vault
}

pre {
self.sentVault.balance == amount:
"Expected amount =".concat(amount.toString()).concat(" but sentVault.balance=").concat(self.sentVault.balance.toString())
}

execute {
// Deposit the amount into the CadenceOwnedAccount if the balance is greater than zero
if self.sentVault.balance > 0.0 {
self.coa.deposit(from: <-self.sentVault)
} else {
destroy self.sentVault
}
}
}
56 changes: 56 additions & 0 deletions cadence/tests/transactions/deploy.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import "FungibleToken"
import "FlowToken"

import "EVM"

/// Deploys a compiled solidity contract from bytecode to the EVM, with the signer's COA as the deployer
///
transaction(bytecode: String, gasLimit: UInt64, value: UFix64) {

let coa: auth(EVM.Deploy) &EVM.CadenceOwnedAccount
var sentVault: @FlowToken.Vault?

prepare(signer: auth(BorrowValue) &Account) {

let storagePath = StoragePath(identifier: "evm")!
self.coa = signer.storage.borrow<auth(EVM.Deploy) &EVM.CadenceOwnedAccount>(from: storagePath)
?? panic("Could not borrow reference to the signer's bridged account")

// Rebalance Flow across VMs if there is not enough Flow in the EVM account to cover the value
let evmFlowBalance: UFix64 = self.coa.balance().inFLOW()
if self.coa.balance().inFLOW() < value {
let withdrawAmount: UFix64 = value - evmFlowBalance
let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(
from: /storage/flowTokenVault
) ?? panic("Could not borrow reference to the owner's Vault!")

self.sentVault <- vaultRef.withdraw(amount: withdrawAmount) as! @FlowToken.Vault
} else {
self.sentVault <- nil
}
}

execute {

// Deposit Flow into the EVM account if necessary otherwise destroy the sent Vault
if self.sentVault != nil {
self.coa.deposit(from: <-self.sentVault!)
} else {
destroy self.sentVault
}

let valueBalance = EVM.Balance(attoflow: 0)
valueBalance.setFLOW(flow: value)
// Finally deploy the contract
let evmResult = self.coa.deploy(
code: bytecode.decodeHex(),
gasLimit: gasLimit,
value: valueBalance
)
assert(
evmResult.status == EVM.Status.successful && evmResult.deployedContract != nil,
message: "EVM deployment failed with error code: ".concat(evmResult.errorCode.toString())
.concat(" and message: ").concat(evmResult.errorMessage)
)
}
}
19 changes: 19 additions & 0 deletions cadence/tests/transactions/move_block.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import "EVM"

transaction {
let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount
prepare(signer: auth(BorrowValue) &Account) {
self.coa = signer.storage.borrow<auth(EVM.Call) &EVM.CadenceOwnedAccount>(from: /storage/evm)
?? panic("A CadenceOwnedAccount (COA) Resource could not be found at path /storage/evm")
}

execute {
let res = self.coa.call(
to: self.coa.address(),
data: [],
gasLimit: 15_000_000,
value: EVM.Balance(attoflow: 0)
)
assert(res.status == EVM.Status.successful, message: "Empty EVM call failed")
}
}
101 changes: 101 additions & 0 deletions cadence/tests/wrap_and_mint_tests.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import Test
import BlockchainHelpers
import "test_helpers.cdc"

import "EVM"

access(all) let serviceAccount = Test.serviceAccount()

access(all) var coaAddress: String = ""
access(all) var wflowAddress: String = ""
access(all) var erc721Address: String = ""

access(all)
fun setup() {
// Create & fund a CadenceOwnedAccount
let coaRes = executeTransaction(
"./transactions/create_coa.cdc",
[100.0],
serviceAccount
)
Test.expect(coaRes, Test.beSucceeded())

// Extract COA address from event
let coaEvts = Test.eventsOfType(Type<EVM.CadenceOwnedAccountCreated>())
let coaEvt = coaEvts[0] as! EVM.CadenceOwnedAccountCreated
coaAddress = coaEvt.address

// Deploy WFLOW
let wflowDeployRes = executeTransaction(
"./transactions/deploy.cdc",
[getWFLOWBytecode(), UInt64(15_000_000), 0.0],
serviceAccount
)
Test.expect(wflowDeployRes, Test.beSucceeded())

// Extract WFLOW address from event
var txnExecEvts = Test.eventsOfType(Type<EVM.TransactionExecuted>())
let wflowEvt = txnExecEvts[2] as! EVM.TransactionExecuted
wflowAddress = wflowEvt.contractAddress

// Deploy ERC721
let constructorArgs = [
"Maybe Mint ERC721",
"MAYBE",
EVM.addressFromString(wflowAddress),
UInt256(1_000_000_000_000_000_000),
EVM.addressFromString(coaAddress)
]
// Encode constructor args as ABI and then as hex
let argsBytecode = String.encodeHex(EVM.encodeABI(
constructorArgs
))
// Append the encoded constructor args to the compiled bytecode
let finalBytecode = String.join([getERC721Bytecode(), argsBytecode], separator: "")
let erc721DeployRes = executeTransaction(
"./transactions/deploy.cdc",
[finalBytecode, UInt64(15_000_000), 0.0],
serviceAccount
)
Test.expect(erc721DeployRes, Test.beSucceeded())

// Extract ERC721 address from event
txnExecEvts = Test.eventsOfType(Type<EVM.TransactionExecuted>())
let erc721Evt = txnExecEvts[3] as! EVM.TransactionExecuted
erc721Address = erc721Evt.contractAddress
}

access(all)
fun testWrapAndMintSucceeds() {
let user = Test.createAccount()
mintFlow(to: user, amount: 10.0)

// Executes the wrap_and_mint.cdc transaction
// - Creates a COA
// - Funds the COA with FLOW to cover mint cost
// - Wraps FLOW as WFLOW
// - Approves ERC721 contract to mint
// - Mints ERC721 <- may fail so we retry here until success (can't mock CadenceArch calls in Cadence tests atm)
wrapAndMintUntilSuccess(iter: 5, signer: user, wflow: wflowAddress, erc721: erc721Address)
}

access(all)
fun wrapAndMintUntilSuccess(iter: Int, signer: Test.TestAccount, wflow: String, erc721: String) {
var i = 0
var success = false
while i < iter {
let res = executeTransaction(
"../transactions/bundled/wrap_and_mint.cdc",
[wflow, erc721],
signer
)
if res.error == nil {
success = true
break
} else {
i = i + 1
moveBlock()
}
}
Test.assert(success)
}
Loading