-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Cadence transaction batching EVM calls (#12)
* forge install: random-coin-toss * remove submodule * forge install: random-coin-toss * update foundry.toml * update foundry remappings * add CadenceArchUtils library & test coverage on random mint * update dependencies * replace local dep with foundry installed dep * add custom errors to ERC721 * add bundled Cadence txn * add stepwise Cadence txns * remove unused GitHub cadence tests workflow * remove WETH9 contract * update transaction comments * update README with testnet deployments * remove onflow/random-coin-toss * add Cadence tests & helper solidity contract * forge install: flow-sol-utils * update solidity dependencies to use flow-sol-utils * fix failing Cadence tests & remove debug tests
- Loading branch information
1 parent
74422d2
commit 5aa3bb2
Showing
14 changed files
with
702 additions
and
39 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)| |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.