github.com/cgcardona/r-subnet-evm@v0.1.5/contracts/test/utils.ts (about) 1 import { ethers } from "hardhat" 2 import { CallOverrides } from "ethers" 3 import assert from "assert" 4 5 /* 6 * 7 * The `test` function is a wrapper around Mocha's `it` function. It provides a normalized framework for running the 8 * majority of your test assertions inside of a smart-contract, using `DS-Test`. 9 * The API can be used as follows (all of the examples are equivalent): 10 * ```ts 11 * test("<test_name>", "<contract_function_name>") 12 * test("<test_name>", ["<contract_function_name>"]) 13 * test("<test_name>", { method: "<contract_function_name>", overrides: {}, shouldFail: false, debug: false }) 14 * test("<test_name>", [{ method: "<contract_function_name>", overrides: {}, shouldFail: false, debug: false }]) 15 * test("<test_name>", [{ method: "<contract_function_name>", shouldFail: false, debug: false }], {}) 16 * ``` 17 * Many contract functions can be called as a part of the same test: 18 * ```ts 19 * test("<test_name>", ["<step_fn1>", "<step_fn2>", "<step_fn3>"]) 20 * ``` 21 * Individual test functions can describe their own overrides with the `overrides` property. 22 * If an object is passed in as the third argument to `test`, it will be used as the default overrides for all test 23 * functions. 24 * The following are equivalent: 25 * ```ts 26 * test("<test_name>", [{ method: "<contract_function_name>", overrides: { from: "0x123" } }]) 27 * test("<test_name>", [{ method: "<contract_function_name>" }], { from: "0x123" }) 28 * ``` 29 * In the above cases, the `from` override must be a signer. 30 * The `shouldFail` property can be used to indicate that the test function should fail. This should be used sparingly 31 * as it is not possible to match on the failure reason. 32 * Furthermore, the `debug` property can be used to print any thrown errors when attempting to 33 * send a transaction or while waiting for the transaction to be confirmed (the transaction is the smart contract call). 34 * `debug` will also cause any parseable event logs to be printed that start with the `log_` prefix. 35 * `DSTest` contracts have several options for emitting `log_` events. 36 * 37 */ 38 39 // Below are the types that help define all the different ways to call `test` 40 type FnNameOrObject = string | string[] | MethodObject | MethodObject[] 41 42 // Limit `from` property to be a `string` instead of `string | Promise<string>` 43 type Overrides = CallOverrides & { from?: string } 44 45 type MethodObject = { method: string, debug?: boolean, overrides?: Overrides, shouldFail?: boolean } 46 47 // This type is after all default values have been applied 48 type MethodWithDebugAndOverrides = MethodObject & { debug: boolean, overrides: Overrides, shouldFail: boolean } 49 50 // `test` is used very similarly to `it` from Mocha 51 export const test = (name, fnNameOrObject, overrides = {}) => it(name, buildTestFn(fnNameOrObject, overrides)) 52 // `test.only` is used very similarly to `it.only` from Mocha, it will isolate all tests marked with `test.only` 53 test.only = (name, fnNameOrObject, overrides = {}) => it.only(name, buildTestFn(fnNameOrObject, overrides)) 54 // `test.debug` is used to apply `debug: true` to all DSTest contract method calls in the test 55 test.debug = (name, fnNameOrObject, overrides = {}) => it.only(name, buildTestFn(fnNameOrObject, overrides, true)) 56 // `test.skip` is used very similarly to `it.skip` from Mocha, it will skip all tests marked with `test.skip` 57 test.skip = (name, fnNameOrObject, overrides = {}) => it.skip(name, buildTestFn(fnNameOrObject, overrides)) 58 59 // `buildTestFn` is a higher-order function. It returns a function that can be used as the test function for `it` 60 const buildTestFn = (fnNameOrObject: FnNameOrObject, overrides = {}, debug = false) => { 61 // normalize the input to an array of objects 62 const fnObjects: MethodWithDebugAndOverrides[] = (Array.isArray(fnNameOrObject) ? fnNameOrObject : [fnNameOrObject]).map(fnNameOrObject => { 63 fnNameOrObject = typeof fnNameOrObject === 'string' ? { method: fnNameOrObject } : fnNameOrObject 64 // assign all default values and overrides 65 fnNameOrObject.overrides = Object.assign({}, overrides, fnNameOrObject.overrides ?? {}) 66 fnNameOrObject.debug = fnNameOrObject.debug ?? debug 67 fnNameOrObject.shouldFail = fnNameOrObject.shouldFail ?? false 68 69 return fnNameOrObject as MethodWithDebugAndOverrides 70 }) 71 72 // only `step_` prefixed functions can be called on the `DSTest` contracts to clearly separate tests and helpers 73 assert(fnObjects.every(({ method }) => method.startsWith('step_')), "Solidity test functions must be prefixed with 'step_'") 74 75 // return the test function that will be used by `it` 76 // this function must be defined with the `function` keyword so that `this` is bound to the Mocha context 77 return async function () { 78 // `Array.prototype.reduce` is used here to ensure that the test functions are called in order. 79 // Each test function waits for its predecessor to complete before starting 80 return fnObjects.reduce((p: Promise<undefined>, fn) => p.then(async () => { 81 const contract = fn.overrides.from 82 ? this.testContract.connect(await ethers.getSigner(fn.overrides.from)) 83 : this.testContract 84 const tx = await contract[fn.method](fn.overrides).catch(err => { 85 if (fn.shouldFail) { 86 if (fn.debug) console.error(`smart contract call failed with error:\n${err}\n`) 87 88 return { failed: true } 89 } 90 91 console.error("smart contract call failed with error:", err) 92 throw err 93 }) 94 95 // no more assertions necessary if the method-call should fail and did fail 96 if (tx.failed && fn.shouldFail) return 97 98 const txReceipt = await tx.wait().catch(err => { 99 if (fn.debug) console.error(`tx failed with error:\n${err}\n`) 100 return err.receipt 101 }) 102 103 // `txReceipt.status` will be `0` if the transaction failed. 104 // `contract.callStatic.failed()` will return `true` if any of the `DSTest` assertions failed. 105 const failed = txReceipt.status === 0 ? true : await contract.callStatic.failed() 106 if (fn.debug || failed) { 107 console.log('') 108 109 if (!txReceipt.events) console.warn('WARNING: No parseable events found in tx-receipt\n') 110 111 // If `DSTest` assertions failed, the contract will emit logs describing the assertion failure(s). 112 txReceipt 113 .events 114 ?.filter(event => fn.debug || event.event?.startsWith('log')) 115 .map(event => event.args?.forEach(arg => console.log(arg))) 116 117 console.log('') 118 } 119 120 if (fn.shouldFail) { 121 assert(failed, `${fn.method} should have failed`) 122 } else { 123 assert(!failed, `${fn.method} failed`) 124 } 125 }), Promise.resolve()) 126 } 127 }