setup github action helpers for running solidity tests

pull/3274/head
Praise Disu 2 years ago committed by Aniket
parent 413a3b155a
commit 9efa9dd973
  1. 15
      libs/ghaction/.eslintrc
  2. 1
      libs/ghaction/.npmignore
  3. 7
      libs/ghaction/README.md
  4. 40
      libs/ghaction/package.json
  5. 22
      libs/ghaction/src/artefacts-helper.ts
  6. 7
      libs/ghaction/src/chai.ts
  7. 9
      libs/ghaction/src/ethers.ts
  8. 2
      libs/ghaction/src/index.ts
  9. 244
      libs/ghaction/src/methods.ts
  10. 42
      libs/ghaction/src/signer.ts
  11. 7
      libs/ghaction/tsconfig.json
  12. 15
      libs/ghaction/tsconfig.lib.json
  13. 3
      tsconfig.paths.json

@ -0,0 +1,15 @@
{
"extends": "../../.eslintrc",
"rules": {
"dot-notation": "off",
"no-unused-vars": "off",
"no-use-before-define": "off"
},
"env": {
"browser": true,
"amd": true,
"node": true,
"es6": true
},
"ignorePatterns": ["!**/*"]
}

@ -0,0 +1,7 @@
# ghaction
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test ghaction` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1,40 @@
{
"name": "@remix-project/ghaction",
"version": "0.1.0",
"description": "Tools to help with github actions for running solidity tests",
"main": "src/index.js",
"types": "src/index.d.ts",
"contributors": [
{
"name": "David Disu",
"email": "daviddisu8@gmail.com"
}
],
"dependencies": {
"ethers": "^5.7.2",
"ganache": "^7.5.0",
"chai": "^4.3.7",
"@remix-project/remix-solidity": "^0.5.5",
"@ethereum-waffle/chai": "^3.4.4"
},
"devDependencies": {
"typescript": "^3.7.4"
},
"scripts": {
"build": "tsc"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ethereum/remix-project.git"
},
"author": "Remix Team",
"license": "MIT",
"bugs": {
"url": "https://github.com/ethereum/remix-project/issues"
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/ghaction#readme",
"typings": "src/index.d.ts"
}

@ -0,0 +1,22 @@
import { CompilationResult } from '@remix-project/remix-solidity'
import * as fs from 'fs/promises'
import * as path from 'path'
declare global {
const remixContractArtefactsPath: string
}
export async function getArtefactsByContractName (contractIdentifier: string) {
const contractArtefacts = await fs.readdir(global.remixContractArtefactsPath)
let contract
for (const artefactFile of contractArtefacts) {
const artefact = await fs.readFile(path.join(global.remixContractArtefactsPath, artefactFile), 'utf-8')
const artefactJSON: CompilationResult = JSON.parse(artefact)
const contractFullPath = (Object.keys(artefactJSON.contracts!)).find((contractName) => artefactJSON.contracts![contractName] && artefactJSON.contracts![contractName][contractIdentifier])
contract = contractFullPath ? artefactJSON.contracts![contractFullPath!][contractIdentifier] : undefined
if (contract) break
}
return contract
}

@ -0,0 +1,7 @@
import * as chai from 'chai'
import { waffleChai } from '@ethereum-waffle/chai'
chai.use(waffleChai)
export * from 'chai'
export { chai }

@ -0,0 +1,9 @@
import { ethers } from 'ethers'
import * as ganache from 'ganache'
import * as hhEtherMethods from './methods'
global.ganacheProvider = ganache.provider({ logging: { quiet: true } })
for(const method in hhEtherMethods) Object.defineProperty(ethers, method, { value: hhEtherMethods[method]})
export * from 'ethers'
export { ethers }

@ -0,0 +1,2 @@
export * from './chai'
export * from './ethers'

@ -0,0 +1,244 @@
import { ethers } from "ethers"
import { getArtefactsByContractName } from './artefacts-helper'
import { SignerWithAddress } from './signer'
declare global {
const ganacheProvider: any
}
const isFactoryOptions = (signerOrOptions) => {
if (!signerOrOptions || signerOrOptions === undefined || signerOrOptions instanceof ethers.Signer) return false
return true
}
const isArtifact = (artifact) => {
const {
contractName,
sourceName,
abi,
bytecode,
deployedBytecode,
linkReferences,
deployedLinkReferences,
} = artifact
return (
typeof contractName === "string" &&
typeof sourceName === "string" &&
Array.isArray(abi) &&
typeof bytecode === "string" &&
typeof deployedBytecode === "string" &&
linkReferences !== undefined &&
deployedLinkReferences !== undefined
)
}
function linkBytecode(artifact, libraries) {
let bytecode = artifact.bytecode
for (const { sourceName, libraryName, address } of libraries) {
const linkReferences = artifact.linkReferences[sourceName][libraryName]
for (const { start, length } of linkReferences) {
bytecode =
bytecode.substr(0, 2 + start * 2) +
address.substr(2) +
bytecode.substr(2 + (start + length) * 2)
}
}
return bytecode
}
const collectLibrariesAndLink = async (artifact, libraries) => {
const neededLibraries = []
for (const [sourceName, sourceLibraries] of Object.entries(artifact.linkReferences)) {
for (const libName of Object.keys(sourceLibraries)) {
neededLibraries.push({ sourceName, libName })
}
}
const linksToApply = new Map()
for (const [linkedLibraryName, linkedLibraryAddress] of Object.entries(libraries)) {
if (!ethers.utils.isAddress(linkedLibraryAddress)) {
throw new Error(
`You tried to link the contract ${artifact.contractName} with the library ${linkedLibraryName}, but provided this invalid address: ${linkedLibraryAddress}`
)
}
const matchingNeededLibraries = neededLibraries.filter((lib) => {
return (
lib.libName === linkedLibraryName ||
`${lib.sourceName}:${lib.libName}` === linkedLibraryName
)
})
if (matchingNeededLibraries.length === 0) {
let detailedMessage
if (neededLibraries.length > 0) {
const libraryFQNames = neededLibraries
.map((lib) => `${lib.sourceName}:${lib.libName}`)
.map((x) => `* ${x}`)
.join("\n")
detailedMessage = `The libraries needed are:
${libraryFQNames}`
} else {
detailedMessage = "This contract doesn't need linking any libraries."
}
throw new Error(
`You tried to link the contract ${artifact.contractName} with ${linkedLibraryName}, which is not one of its libraries.
${detailedMessage}`
)
}
if (matchingNeededLibraries.length > 1) {
const matchingNeededLibrariesFQNs = matchingNeededLibraries
.map(({ sourceName, libName }) => `${sourceName}:${libName}`)
.map((x) => `* ${x}`)
.join("\n")
throw new Error(
`The library name ${linkedLibraryName} is ambiguous for the contract ${artifact.contractName}.
It may resolve to one of the following libraries:
${matchingNeededLibrariesFQNs}
To fix this, choose one of these fully qualified library names and replace where appropriate.`
)
}
const [neededLibrary] = matchingNeededLibraries
const neededLibraryFQN = `${neededLibrary.sourceName}:${neededLibrary.libName}`
// The only way for this library to be already mapped is
// for it to be given twice in the libraries user input:
// once as a library name and another as a fully qualified library name.
if (linksToApply.has(neededLibraryFQN)) {
throw new Error(
`The library names ${neededLibrary.libName} and ${neededLibraryFQN} refer to the same library and were given as two separate library links.
Remove one of them and review your library links before proceeding.`
)
}
linksToApply.set(neededLibraryFQN, {
sourceName: neededLibrary.sourceName,
libraryName: neededLibrary.libName,
address: linkedLibraryAddress,
})
}
if (linksToApply.size < neededLibraries.length) {
const missingLibraries = neededLibraries
.map((lib) => `${lib.sourceName}:${lib.libName}`)
.filter((libFQName) => !linksToApply.has(libFQName))
.map((x) => `* ${x}`)
.join("\n")
throw new Error(
`The contract ${artifact.contractName} is missing links for the following libraries:
${missingLibraries}`
)
}
return linkBytecode(artifact, [...linksToApply.values()])
}
// Convert output.contracts.<filename>.<contractName> in Artifact object compatible form
const resultToArtifact = (result) => {
const { fullyQualifiedName, artefact } = result
return {
contractName: fullyQualifiedName.split(':')[1],
sourceName: fullyQualifiedName.split(':')[0],
abi: artefact.abi,
bytecode: artefact.evm.bytecode.object,
deployedBytecode: artefact.evm.deployedBytecode.object,
linkReferences: artefact.evm.bytecode.linkReferences,
deployedLinkReferences: artefact.evm.deployedBytecode.linkReferences
}
}
const getContractFactory = async (contractNameOrABI: ethers.ContractInterface, bytecode?: string, signerOrOptions = null) => {
if (bytecode && contractNameOrABI) {
return new ethers.ContractFactory(contractNameOrABI, bytecode, signerOrOptions || (new ethers.providers.Web3Provider(ganacheProvider)).getSigner())
} else if (typeof contractNameOrABI === 'string') {
const contract = await getArtefactsByContractName(contractNameOrABI)
if (contract) {
return new ethers.ContractFactory(contract.abi, contract.evm.bytecode.object, signerOrOptions || (new ethers.providers.Web3Provider(ganacheProvider)).getSigner())
} else {
throw new Error('Contract artefacts not found')
}
} else {
throw new Error('Invalid contract name or ABI provided')
}
}
const getContractAt = async (contractNameOrABI: ethers.ContractInterface, address: string, signer = null) => {
const provider = new ethers.providers.Web3Provider(ganacheProvider)
if(typeof contractNameOrABI === 'string') {
try {
const result = await getArtefactsByContractName(contractNameOrABI)
if (result) {
return new ethers.Contract(address, result.abi, signer || provider.getSigner())
} else {
throw new Error('Contract artefacts not found')
}
} catch(e) { throw e }
} else {
return new ethers.Contract(address, contractNameOrABI, signer || provider.getSigner())
}
}
const getSigner = async (address: string) => {
const provider = new ethers.providers.Web3Provider(ganacheProvider)
const signer = provider.getSigner(address)
return SignerWithAddress.create(signer)
}
const getSigners = async () => {
try {
const provider = new ethers.providers.Web3Provider(ganacheProvider)
const accounts = await provider.listAccounts()
return await Promise.all( accounts.map((account) => getSigner(account)))
} catch(err) { throw err }
}
const getContractFactoryFromArtifact = async (artifact, signerOrOptions = null) => {
let libraries = {}
let signer
if (!isArtifact(artifact)) {
throw new Error(
`You are trying to create a contract factory from an artifact, but you have not passed a valid artifact parameter.`
)
}
if (isFactoryOptions(signerOrOptions)) {
signer = signerOrOptions.signer;
libraries = signerOrOptions.libraries ?? {};
} else {
signer = signerOrOptions;
}
if (artifact.bytecode === "0x") {
throw new Error(
`You are trying to create a contract factory for the contract ${artifact.contractName}, which is abstract and can't be deployed.
If you want to call a contract using ${artifact.contractName} as its interface use the "getContractAt" function instead.`
)
}
const linkedBytecode = await collectLibrariesAndLink(artifact, libraries)
return new ethers.ContractFactory(artifact.abi, linkedBytecode || artifact.bytecode, signer || (new ethers.providers.Web3Provider(web3Provider)).getSigner())
}
const getContractAtFromArtifact = async (artifact, address, signerOrOptions = null) => {
if (!isArtifact(artifact)) {
throw new Error(
`You are trying to create a contract factory from an artifact, but you have not passed a valid artifact parameter.`
)
}
return await getContractAt(artifact.abi, address, signerOrOptions)
}
export { getContractAtFromArtifact, getContractFactoryFromArtifact, getSigners, getSigner, getContractAt, getContractFactory }

@ -0,0 +1,42 @@
import { ethers } from "ethers"
export class SignerWithAddress extends ethers.Signer {
static async create(signer) {
return new SignerWithAddress(await signer.getAddress(), signer)
}
constructor(address, _signer) {
super()
this.address = address
this._signer = _signer
this.provider = _signer.provider
}
async getAddress() {
return this.address
}
signMessage(message){
return this._signer.signMessage(message)
}
signTransaction(transaction) {
return this._signer.signTransaction(transaction)
}
sendTransaction(transaction) {
return this._signer.sendTransaction(transaction)
}
connect(provider) {
return new SignerWithAddress(this.address, this._signer.connect(provider))
}
_signTypedData(...params) {
return this._signer._signTypedData(...params)
}
toJSON() {
return `<SignerWithAddress ${this.address}>`
}
}

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["jest", "node"]
},
"include": ["**/*.ts"]
}

@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "../../dist/out-tsc",
"declaration": true,
"rootDir": "./src",
"types": ["node"]
},
"exclude": [
"**/*.spec.ts"
],
"include": ["**/*.ts"]
}

@ -145,6 +145,9 @@
"@remix-ui/locale-module": [
"libs/remix-ui/locale-module/src/index.ts"
],
"@remixproject/ghaction-helper": [
"libs/ghaction-helper/src/index.ts"
]
}
}
}
Loading…
Cancel
Save