@onflow/flow-js-testing
Version:
This package will expose a set of utility methods, to allow Cadence code testing with libraries like Jest
183 lines (154 loc) • 5.66 kB
JavaScript
/*
* Flow JS Testing
*
* Copyright 2020-2021 Dapper Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {sendTransaction} from "./interaction"
import {getServiceAddress} from "./utils"
import {defaultsByName, getContractCode} from "./file"
import txRegistry from "./generated/transactions"
import {isObject} from "./utils"
import {
extractContractParameters,
generateSchema,
splitArgs,
} from "@onflow/flow-cadut"
import {replaceImportAddresses, resolveImports} from "./imports"
import {applyTransformers, builtInMethods} from "./transformers"
const {updateContractTemplate, deployContractTemplate} = txRegistry
export const hexContract = contract =>
Buffer.from(contract, "utf8").toString("hex")
const extractParameters = async params => {
let ixName, ixTo, ixAddressMap, ixArgs, ixUpdate
if (isObject(params[0])) {
const [props] = params
const {name, to, addressMap, args, update} = props
if (!name) {
throw Error("'name' field is missing")
}
ixName = name
ixTo = to
ixArgs = args
ixAddressMap = addressMap
ixUpdate = update
} else {
;[ixName, ixTo, ixAddressMap, ixArgs, ixUpdate] = params
}
const serviceAddress = await getServiceAddress()
const addressMap = {
...defaultsByName,
FlowManager: serviceAddress,
...ixAddressMap,
}
return {
name: ixName,
to: ixTo,
args: ixArgs,
update: ixUpdate,
addressMap,
}
}
/**
* Deploys a contract by name to specified account
* Returns transaction result.
* @param {string} props.to - If no address is supplied, the contract will be deployed to the emulator service account.
* @param {string} props.name - The name of the contract to look for. This should match a .cdc file located at the specified `basePath`.
* @param {{string:string}} [props.addressMap={}] - name/address map to use as lookup table for addresses in import statements.
* @param {boolean} [props.update=false] - flag to indicate whether the contract shall be replaced.
* @returns {Promise<any>}
*/
export const deployContractByName = async (...props) => {
const params = await extractParameters(props)
const {to, name, addressMap, args, update = false} = params
const resolvedAddress = to || (await getServiceAddress())
const contractCode = await getContractCode({name, addressMap})
const ixName = /[\\/]/.test(name) ? null : name
return deployContract({
to: resolvedAddress,
code: contractCode,
name: ixName,
args,
update,
})
}
/**
* Deploys contract as Cadence code to specified account
* Returns transaction result.
* @param {Object} props
* @param {string} props.code - Cadence code for contract to be deployed
* @param {string} props.to - If no address is supplied, the contract
* will be deployed to the emulator service account
* @param {string} props.name - The name of the contract to look for. This should match
* a .cdc file located at the specified `basePath`
* @param {{string:string}} [props.addressMap={}] - name/address map to use as lookup table for addresses in import statements.
* @param {boolean} [props.update=false] - flag to indicate whether the contract shall be replaced
* @param {[(code: string) => string]} [props.transformers] - code transformers to apply to contract
*/
export const deployContract = async props => {
const {
to,
code: rawContractCode,
name,
args,
update,
transformers = [],
} = props
const params = await extractContractParameters(rawContractCode)
const ixName = name || params.contractName
// TODO: extract name from contract code
const containerAddress = to || (await getServiceAddress())
const managerAddress = await getServiceAddress()
// Resolve contract import addresses
const deployedContracts = await resolveImports(rawContractCode)
const serviceAddress = await getServiceAddress()
const addressMap = {
...defaultsByName,
...deployedContracts,
FlowManager: serviceAddress,
}
// Replace contract import addresses
let contractCode = replaceImportAddresses(rawContractCode, addressMap)
// Apply code transformers
contractCode = await applyTransformers(contractCode, [
...transformers,
builtInMethods,
])
const hexedCode = hexContract(contractCode)
let code = update
? await updateContractTemplate(addressMap)
: await deployContractTemplate(addressMap)
let deployArgs = [ixName, hexedCode, managerAddress]
if (args) {
deployArgs = deployArgs.concat(args)
const schema = generateSchema(params.args).map(item => splitArgs(item)[0])
const argLetter = "abcdefghijklmnopqrstuvwxyz"
let argList = []
for (let i = 0; i < schema.length; i++) {
const value = schema[i]
argList.push(`${argLetter[i]}: ${value}`)
}
code = code.replace("##ARGS-WITH-TYPES##", `, ${params.args}`)
code = code.replace("##ARGS-LIST##", argList)
} else {
code = code.replace("##ARGS-WITH-TYPES##", ``)
code = code.replace("##ARGS-LIST##", "")
}
const signers = [containerAddress]
return sendTransaction({
code,
args: deployArgs,
signers,
})
}