All files / src interaction.js

100% Statements 78/78
100% Branches 27/27
100% Functions 11/11
100% Lines 77/77

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212                                                        12x 522x     522x 505x                 505x   505x   505x 2x       503x 503x   503x 503x 503x 503x   17x 10x   7x         520x   520x   19x 19x         520x 365x   155x       520x 520x             520x     520x         520x                                       12x   94x 94x 94x 94x 94x 94x 94x   93x     93x               93x 31x 31x     62x       93x 75x 75x   93x 93x   2x     94x                     12x   425x 425x 425x 425x 425x 425x 425x   424x   424x 169x 169x   424x 358x   67x     425x     12x 519x 519x 31x   519x 519x 519x 519x 519x     519x 519x    
/*
 * 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 * as fcl from "@onflow/fcl"
import {resolveArguments} from "@onflow/flow-cadut"
import {authorization} from "./crypto"
import emulator from "./emulator/emulator"
import {getTransactionCode, getScriptCode, defaultsByName} from "./file"
import {resolveImports, replaceImportAddresses} from "./imports"
import {getServiceAddress} from "./utils"
import {applyTransformers, builtInMethods} from "./transformers"
import {isObject} from "./utils"
 
export const extractParameters = ixType => {
  return async params => {
    let ixCode, ixName, ixSigners, ixArgs, ixService, ixTransformers, ixLimit
 
    if (isObject(params[0])) {
      const [props] = params
      const {
        name,
        code,
        args,
        signers,
        transformers,
        limit,
        service = false,
      } = props
 
      ixService = service
 
      if (!name && !code) {
        throw Error(
          "Both `name` and `code` are missing. Provide either of them"
        )
      }
      ixName = name
      ixCode = code
 
      ixSigners = signers
      ixArgs = args
      ixTransformers = transformers || []
      ixLimit = limit
    } else {
      if (ixType === "script") {
        ;[ixName, ixArgs, ixLimit, ixTransformers = []] = params
      } else {
        ;[ixName, ixSigners, ixArgs, ixLimit, ixTransformers = []] = params
      }
    }
 
    // Check that limit is always set
    ixLimit = ixLimit || (await fcl.config().get("fcl.limit"))
 
    if (ixName) {
      const getIxTemplate =
        ixType === "script" ? getScriptCode : getTransactionCode
      ixCode = await getIxTemplate({name: ixName})
    }
 
    // We need a way around to allow initial scripts and transactions for Manager contract
    let deployedContracts
    if (ixService) {
      deployedContracts = defaultsByName
    } else {
      deployedContracts = await resolveImports(ixCode)
    }
 
    // Resolve default import addresses
    const serviceAddress = await getServiceAddress()
    const addressMap = {
      ...defaultsByName,
      ...deployedContracts,
      FlowManager: serviceAddress,
    }
 
    // Replace code import addresses
    ixCode = replaceImportAddresses(ixCode, addressMap)
 
    // Apply all the necessary transformations to the code
    ixCode = await applyTransformers(ixCode, [
      ...ixTransformers,
      builtInMethods,
    ])
 
    return {
      code: ixCode,
      signers: ixSigners,
      args: ixArgs,
      limit: ixLimit,
    }
  }
}
 
/**
 * Submits transaction to emulator network and waits before it will be sealed.
 * Returns transaction result.
 * @param {Object} props
 * @param {string} [props.name] - Name of Cadence template file
 * @param {{string:string}} [props.addressMap={}] - name/address map to use as lookup table for addresses in import statements.
 * @param {string} [props.code] - Cadence code of the transaction.
 * @param {[any]} [props.args] - array of arguments specified as tupple, where last value is the type of preceding values.
 * @param {[string]} [props.signers] - list of signers, who will authorize transaction, specified as array of addresses.
 * @returns {Promise<any>}
 */
export const sendTransaction = async (...props) => {
  // This is here to fix an issue with microbundler confusing argument scopes
  let _props = props
  let result = null,
    err = null
  const logs = await captureLogs(async () => {
    try {
      const extractor = extractParameters("tx")
      const {code, args, signers, limit} = await extractor(_props)
 
      const serviceAuth = authorization()
 
      // set repeating transaction code
      const ix = [
        fcl.transaction(code),
        fcl.payer(serviceAuth),
        fcl.proposer(serviceAuth),
        fcl.limit(limit),
      ]
 
      // use signers if specified
      if (signers) {
        const auths = signers.map(signer => authorization(signer))
        ix.push(fcl.authorizations(auths))
      } else {
        // and only service account if no signers
        ix.push(fcl.authorizations([serviceAuth]))
      }
 
      // add arguments if any
      if (args) {
        const resolvedArgs = await resolveArguments(args, code)
        ix.push(fcl.args(resolvedArgs))
      }
      const response = await fcl.send(ix)
      result = await fcl.tx(response).onceExecuted()
    } catch (e) {
      err = e
    }
  })
  return [result, err, logs]
}
 
/**
 * Sends script code for execution. Returns decoded value
 * @param {Object} props
 * @param {string} props.code - Cadence code of the script to be submitted.
 * @param {string} props.name - name of the file to source code from.
 * @param {[any]} props.args - array of arguments specified as tuple, where last value is the type of preceding values.
 * @returns {Promise<*>}
 */
export const executeScript = async (...props) => {
  // This is here to fix an issue with microbundler confusing argument scopes
  let _props = props
  let result = null,
    err = null
  const logs = await captureLogs(async () => {
    try {
      const extractor = extractParameters("script")
      const {code, args, limit} = await extractor(_props)
 
      const ix = [fcl.script(code), fcl.limit(limit)]
      // add arguments if any
      if (args) {
        const resolvedArgs = await resolveArguments(args, code)
        ix.push(fcl.args(resolvedArgs))
      }
      const response = await fcl.send(ix)
      result = await fcl.decode(response)
    } catch (e) {
      err = e
    }
  })
  return [result, err, logs]
}
 
export const captureLogs = async callback => {
  const logs = []
  const listener = msg => {
    logs.push(msg)
  }
  emulator.logger.on("log", listener)
  await callback()
  await new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, 50)
  })
  emulator.logger.removeListener("log", listener)
  return logs
}