├── .npmignore ├── test ├── fixture-projects │ └── hardhat-project │ │ ├── .gitignore │ │ ├── contracts │ │ ├── IGreeter.sol │ │ ├── AmbiguousLibrary2.sol │ │ ├── NonUniqueLibrary.sol │ │ ├── TestContractLib.sol │ │ ├── TestNonUniqueLib.sol │ │ ├── Greeter.sol │ │ ├── AmbiguousLibrary.sol │ │ └── GreeterWithArgs.sol │ │ └── hardhat.config.js ├── helpers.ts ├── hethers-provider-wrapper.ts ├── updatable-target-proxy.ts └── index.ts ├── src ├── dist │ └── src │ │ └── signer-with-address.ts ├── tsconfig.json └── internal │ ├── provider-proxy.ts │ ├── hethers-provider-wrapper.ts │ ├── index.ts │ ├── type-extensions.ts │ ├── updatable-target-proxy.ts │ ├── signers.ts │ └── helpers.ts ├── .mocharc.json ├── tsconfig-common.json ├── tsconfig.json ├── .env-template ├── CHANGELOG.md ├── scripts ├── update-version.js ├── create-release.js └── helpers.js ├── .github └── workflows │ ├── publish.yml │ └── nodejs.yml ├── LICENSE ├── package.json ├── .gitignore └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | scripts 3 | test 4 | build-test 5 | .env-template -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project/.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | artifacts/ 3 | -------------------------------------------------------------------------------- /src/dist/src/signer-with-address.ts: -------------------------------------------------------------------------------- 1 | // export { SignerWithAddress } from "../../signers"; 2 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "ts-node/register/files", 3 | "ignore": ["test/fixture-projects/**/*"], 4 | "timeout": 10000 5 | } 6 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project/contracts/IGreeter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | interface IGreeter { 4 | function greet() external view returns (string memory); 5 | } -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project/contracts/AmbiguousLibrary2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | library AmbiguousLibrary { 4 | function libDo(uint256 n) external returns (uint256) { 5 | return n * 2; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project/contracts/NonUniqueLibrary.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | library NonUniqueLibrary { 4 | function libDo(uint256 n) external returns (uint256) { 5 | return n * 2; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../tsconfig-common.json", 3 | "compilerOptions": { 4 | "outDir": "../", 5 | "rootDirs": [ 6 | "./" 7 | ], 8 | "composite": true 9 | }, 10 | "include": ["./**/*.ts"], 11 | "exclude": [] 12 | } -------------------------------------------------------------------------------- /tsconfig-common.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "sourceMap": true, 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "typeRoots": ["../../node_modules/@types", "./@types"], 11 | "noEmitOnError": true, 12 | "skipDefaultLibCheck": true, 13 | "skipLibCheck": true 14 | } 15 | } -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project/contracts/TestContractLib.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | library TestLibrary { 4 | function libDo(uint256 n) external returns (uint256) { 5 | return n * 2; 6 | } 7 | } 8 | 9 | contract TestContractLib { 10 | 11 | function printNumber(uint256 amount) public returns (uint256) { 12 | uint result = TestLibrary.libDo(amount); 13 | return result; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-common.json", 3 | "compilerOptions": { 4 | "outDir": "./build-test", 5 | "rootDirs": [ 6 | "./test" 7 | ], 8 | "composite": true 9 | }, 10 | "include": [ 11 | "./test/**/*.ts" 12 | ], 13 | "exclude": [ 14 | "./node_modules", 15 | "./test/**/hardhat.config.ts" 16 | ], 17 | "references": [ 18 | { 19 | "path": "./src" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project/contracts/TestNonUniqueLib.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | library NonUniqueLibrary { 4 | function libDo(uint256 n) external returns (uint256) { 5 | return n * 2; 6 | } 7 | } 8 | 9 | contract TestNonUniqueLib { 10 | 11 | function printNumber(uint256 amount) public returns (uint256) { 12 | uint result = NonUniqueLibrary.libDo(amount); 13 | return result; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project/contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | contract Greeter { 4 | 5 | string greeting; 6 | 7 | event GreetingUpdated(string greeting); 8 | 9 | constructor() public { 10 | greeting = "Hi"; 11 | } 12 | 13 | function setGreeting(string memory _greeting) public { 14 | greeting = _greeting; 15 | emit GreetingUpdated(_greeting); 16 | } 17 | 18 | function greet() public view returns (string memory) { 19 | return greeting; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /.env-template: -------------------------------------------------------------------------------- 1 | TESTNET_PRIVATEKEY_1=0x0000000000000000000000000000000000000000000000000000000000000000 2 | TESTNET_ACCOUNT_ID_1=x.x.xxxxxxxx 3 | TESTNET_PRIVATEKEY_2=0x0000000000000000000000000000000000000000000000000000000000000000 4 | TESTNET_ACCOUNT_ID_2=x.x.xxxxxxxx 5 | 6 | PREVIEWNET_PRIVATEKEY_1=0x0000000000000000000000000000000000000000000000000000000000000000 7 | PREVIEWNET_ACCOUNT_ID_1=x.x.xxxxxxxx 8 | PREVIEWNET_PRIVATEKEY_2=0x0000000000000000000000000000000000000000000000000000000000000000 9 | PREVIEWNET_ACCOUNT_ID_2=x.x.xxxxxxxx 10 | 11 | RUN_TEST_ON=TESTNET -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project/contracts/AmbiguousLibrary.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import { AmbiguousLibrary as AmbiguousLibrary2 } from "./AmbiguousLibrary2.sol"; 4 | 5 | library AmbiguousLibrary { 6 | function libDo(uint256 n) external returns (uint256) { 7 | return n * 2; 8 | } 9 | } 10 | 11 | contract TestAmbiguousLib { 12 | function printNumber(uint256 amount) public returns (uint256) { 13 | uint result = AmbiguousLibrary.libDo(amount); 14 | result = AmbiguousLibrary2.libDo(result); 15 | return result; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project/contracts/GreeterWithArgs.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | contract GreeterWithArgs { 4 | 5 | string greeting; 6 | 7 | event GreetingUpdated(string greeting); 8 | 9 | constructor(string memory _greeting) public { 10 | greeting = _greeting; 11 | } 12 | 13 | function setGreeting(string memory _greeting) public { 14 | greeting = _greeting; 15 | emit GreetingUpdated(_greeting); 16 | } 17 | 18 | function greet() public view returns (string memory) { 19 | return greeting; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /test/helpers.ts: -------------------------------------------------------------------------------- 1 | import 'mocha'; 2 | import path from 'path'; 3 | import {resetHardhatContext} from 'hardhat/plugins-testing'; 4 | import '../src/internal/type-extensions'; 5 | 6 | export function useEnvironment( 7 | fixtureProjectName: string, 8 | networkName = 'localhost' 9 | ) { 10 | beforeEach('Loading hardhat environment', function () { 11 | process.chdir(path.join(__dirname, 'fixture-projects', fixtureProjectName)); 12 | process.env.HARDHAT_NETWORK = networkName; 13 | 14 | this.env = require('hardhat'); 15 | }); 16 | 17 | afterEach('Resetting hardhat', function () { 18 | resetHardhatContext(); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @nomiclabs/hardhat-ethers 2 | 3 | ## 2.0.5 4 | 5 | ### Patch Changes 6 | 7 | - 1de2a228: Fix an issue that was causing typescript projects to also compile Hardhat's source (#2260). 8 | 9 | ## 2.0.4 10 | 11 | ### Patch Changes 12 | 13 | - 6afeeffe: Add equivalents in hardhat-ethers for `getContractFactory` and `getContractAt` that support passing `Artifact`, specifically `getContractFactoryFromArtifact` and `getContractAtFromArtifact` (issue #1716) 14 | 15 | ## 2.0.3 16 | 17 | ### Patch Changes 18 | 19 | - def9cbb2: Reset the hardhat-ethers provider when a snapshot is reverted (issue #1247) 20 | - 571ef80d: Adds a custom formatter to better display BigNumber's in Hardhat console (issue #2109). 21 | -------------------------------------------------------------------------------- /scripts/update-version.js: -------------------------------------------------------------------------------- 1 | const {exec} = require('child_process'); 2 | const {getLatestRelease} = require('./helpers'); 3 | 4 | (async function() { 5 | let latestRelease = await getLatestRelease(); 6 | if (latestRelease && latestRelease.prerelease) { 7 | let name = latestRelease.name.toLowerCase(); 8 | 9 | let versionType = 'patch'; 10 | 11 | if (name.includes('minor')) { 12 | versionType = 'minor'; 13 | } 14 | else if (name.includes('major')) { 15 | versionType = 'major'; 16 | } 17 | 18 | try { 19 | await exec(`npm version ${versionType} --no-git-tag-version`); 20 | } 21 | catch(err) { 22 | console.error(err); 23 | } 24 | } 25 | })(); -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [prereleased] 6 | 7 | env: 8 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | jobs: 11 | publish: 12 | runs-on: macos-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | ref: 'main' 17 | - uses: actions/setup-node@v2 18 | with: 19 | node-version: 12 20 | - run: npm ci && npm run build 21 | - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 22 | - run: GITHUB_TOKEN=$GITHUB_TOKEN node scripts/update-version.js 23 | - uses: EndBug/add-and-commit@v8.0.2 24 | with: 25 | commit: --signoff 26 | default_author: user_info 27 | message: 'Updating version' 28 | pathspec_error_handling: ignore 29 | - run: npm publish 30 | - run: GITHUB_TOKEN=$GITHUB_TOKEN node scripts/create-release.js -------------------------------------------------------------------------------- /scripts/create-release.js: -------------------------------------------------------------------------------- 1 | const { exec } = require("child_process"); 2 | const { getLatestRelease, deleteRelease, createRelease } = require("./helpers"); 3 | 4 | const sleep = async (ms) => { 5 | return new Promise(resolve => setTimeout(resolve, ms)); 6 | }; 7 | 8 | (async () => { 9 | let latestRelease = await getLatestRelease(); 10 | if (latestRelease && latestRelease.prerelease) { 11 | if (latestRelease && latestRelease.prerelease) { 12 | await deleteRelease(latestRelease.id.toString()); 13 | 14 | const { tag_name } = latestRelease; 15 | 16 | // Delete the tag: 17 | await exec(`git push --delete origin ${tag_name}`); 18 | 19 | // Recreate the tag: 20 | await exec(`git tag ${tag_name}`); 21 | await exec(`git push origin --tags`); 22 | } 23 | 24 | latestRelease.name = latestRelease.name 25 | .replace("minor", "") 26 | .replace("major", "") 27 | .trim(); 28 | 29 | await sleep(5000); 30 | await createRelease(latestRelease); 31 | 32 | } 33 | })(); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 LimeChain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/helpers.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const githubRepo = "LimeChain/hardhat-hethers"; 3 | 4 | async function getLatestRelease() { 5 | const res = await axios.get(`https://api.github.com/repos/${githubRepo}/releases`, { 6 | headers: { "authorization": `Bearer ${process.env.GITHUB_TOKEN}` } 7 | }); 8 | 9 | return res.data[0]; 10 | } 11 | 12 | async function deleteRelease(releaseId) { 13 | return axios.delete(`https://api.github.com/repos/${githubRepo}/releases/${releaseId}`, { 14 | headers: { "authorization": `Bearer ${process.env.GITHUB_TOKEN}` } 15 | }); 16 | } 17 | 18 | async function createRelease(data) { 19 | const { tag_name, target_commitish, name, body } = data; 20 | return axios({ 21 | method: "post", 22 | url: `https://api.github.com/repos/${githubRepo}/releases`, 23 | data: { 24 | tag_name, 25 | target_commitish, 26 | name, 27 | body, 28 | draft: false, 29 | prerelease: false 30 | }, 31 | headers: { "authorization": `Bearer ${process.env.GITHUB_TOKEN}` } 32 | }); 33 | } 34 | 35 | module.exports = { getLatestRelease, deleteRelease, createRelease }; -------------------------------------------------------------------------------- /test/fixture-projects/hardhat-project/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("../../../src/internal"); 2 | const path = require('path'); 3 | require('dotenv').config({path: path.resolve(__dirname, '../../../.env')}); 4 | 5 | module.exports = { 6 | solidity: "0.8.4", 7 | defaultNetwork: "testnet", 8 | hedera: { 9 | gasLimit: 300000, 10 | networks: { 11 | testnet: { 12 | accounts: [ 13 | { 14 | "account": process.env['TESTNET_ACCOUNT_ID_1'], 15 | "privateKey": process.env['TESTNET_PRIVATEKEY_1'] 16 | }, 17 | { 18 | "account": process.env['TESTNET_ACCOUNT_ID_2'], 19 | "privateKey": process.env['TESTNET_PRIVATEKEY_2'] 20 | } 21 | ] 22 | }, 23 | previewnet: { 24 | accounts: [ 25 | { 26 | "account": process.env['PREVIEWNET_ACCOUNT_ID_1'], 27 | "privateKey": process.env['PREVIEWNET_PRIVATEKEY_1'] 28 | }, 29 | { 30 | "account": process.env['PREVIEWNET_ACCOUNT_ID_2'], 31 | "privateKey": process.env['PREVIEWNET_PRIVATEKEY_2'] 32 | } 33 | ] 34 | } 35 | } 36 | }, 37 | }; -------------------------------------------------------------------------------- /src/internal/provider-proxy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HARDHAT_NETWORK_RESET_EVENT, 3 | HARDHAT_NETWORK_REVERT_SNAPSHOT_EVENT, 4 | } from "hardhat/internal/constants"; 5 | import {hethers} from "@hashgraph/hethers"; 6 | 7 | import {HethersProviderWrapper} from "./hethers-provider-wrapper"; 8 | import {createUpdatableTargetProxy} from "./updatable-target-proxy"; 9 | 10 | /** 11 | * This method returns a proxy that uses an underlying provider for everything. 12 | * 13 | * This underlying provider is replaced by a new one after a successful hardhat_reset, 14 | * because hethers providers can have internal state that returns wrong results after 15 | * the network is reset. 16 | */ 17 | export function createProviderProxy( 18 | hardhatProvider: hethers.providers.BaseProvider 19 | ): any { 20 | const initialProvider = new HethersProviderWrapper(hardhatProvider); 21 | const {proxy: providerProxy, setTarget} = createUpdatableTargetProxy(initialProvider); 22 | 23 | hardhatProvider.on(HARDHAT_NETWORK_RESET_EVENT, () => { 24 | setTarget(new HethersProviderWrapper(hardhatProvider)); 25 | }); 26 | hardhatProvider.on(HARDHAT_NETWORK_REVERT_SNAPSHOT_EVENT, () => { 27 | setTarget(new HethersProviderWrapper(hardhatProvider)); 28 | }); 29 | 30 | return providerProxy; 31 | } 32 | -------------------------------------------------------------------------------- /src/internal/hethers-provider-wrapper.ts: -------------------------------------------------------------------------------- 1 | import {hethers} from "@hashgraph/hethers"; 2 | import {HederaAccount} from "./type-extensions"; 3 | 4 | export class HethersProviderWrapper extends hethers.providers.BaseProvider { 5 | private readonly _hardhatProvider: hethers.providers.BaseProvider; 6 | 7 | constructor(hardhatProvider: hethers.providers.BaseProvider) { 8 | let networkConfig: { [url: string]: string } = {}; 9 | hardhatProvider.getHederaClient()._network._network.forEach((obj: any) => { 10 | const address = obj[0]._address; 11 | let addrString = address._address; 12 | if (address._port) addrString = `${addrString}:${address._port}`; 13 | networkConfig[addrString] = obj[0]._accountId.toString(); 14 | }); 15 | 16 | super({ 17 | network: networkConfig, 18 | mirrorNodeUrl: hardhatProvider.getHederaClient().mirrorNetwork[0] 19 | }); 20 | this._network.chainId = hardhatProvider._network.chainId; 21 | 22 | this._hardhatProvider = hardhatProvider; 23 | } 24 | 25 | public getSigner(identifier: HederaAccount): hethers.Wallet { 26 | // @ts-ignore 27 | return new hethers.Wallet(identifier, this._hardhatProvider); 28 | } 29 | 30 | public listAccounts(): any { 31 | const hre = require('hardhat'); 32 | return hre.config.networks[this._hardhatProvider._network.name]?.accounts || []; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/internal/index.ts: -------------------------------------------------------------------------------- 1 | import {hethers} from "@hashgraph/hethers"; 2 | import {extendConfig, extendEnvironment} from "hardhat/config"; 3 | import {lazyObject} from "hardhat/plugins"; 4 | import { 5 | getInitialHederaProvider, 6 | getSigners, 7 | getSigner, 8 | getContractAt, 9 | getContractAtFromArtifact, 10 | getContractFactory, 11 | getContractFactoryFromArtifact, 12 | } from "./helpers"; 13 | import {HederaHardhatConfig, HederaHardhatRuntimeEnvironment} from "./type-extensions"; 14 | 15 | extendConfig( 16 | (config: HederaHardhatConfig) => { 17 | config.networks = {...config.networks, ...config.hedera!.networks}; 18 | } 19 | ); 20 | 21 | extendEnvironment((hre: HederaHardhatRuntimeEnvironment) => { 22 | hre.network.provider = getInitialHederaProvider(hre); 23 | 24 | // @ts-ignore 25 | hre.hethers = lazyObject(() => { 26 | const {createProviderProxy} = require("./provider-proxy"); 27 | 28 | const providerProxy = createProviderProxy(hre.network.provider); 29 | hre.network.provider = providerProxy; 30 | 31 | return { 32 | ...hethers, 33 | 34 | provider: providerProxy, 35 | getSigners: () => getSigners(hre), 36 | getSigner: (identifier: any) => getSigner(hre, identifier), 37 | getContractFactory: getContractFactory.bind(null, hre) as any, 38 | getContractFactoryFromArtifact: getContractFactoryFromArtifact.bind(null, hre), 39 | getContractAt: getContractAt.bind(null, hre), 40 | getContractAtFromArtifact: getContractAtFromArtifact.bind(null, hre), 41 | }; 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [main, develop] 6 | pull_request: 7 | 8 | env: 9 | testnet_account_id_1: ${{ secrets.TESTNET_ACCOUNT_ID_1 }} 10 | testnet_privatekey_1: ${{ secrets.TESTNET_PRIVATEKEY_1 }} 11 | testnet_account_id_2: ${{ secrets.TESTNET_ACCOUNT_ID_2 }} 12 | testnet_privatekey_2: ${{ secrets.TESTNET_PRIVATEKEY_2 }} 13 | previewnet_account_id_1: ${{ secrets.PREVIEWNET_ACCOUNT_ID_1 }} 14 | previewnet_privatekey_1: ${{ secrets.PREVIEWNET_PRIVATEKEY_1 }} 15 | previewnet_account_id_2: ${{ secrets.PREVIEWNET_ACCOUNT_ID_2 }} 16 | previewnet_privatekey_2: ${{ secrets.PREVIEWNET_PRIVATEKEY_2 }} 17 | run_test_on: ${{ secrets.RUN_TEST_ON }} 18 | 19 | jobs: 20 | 21 | test-node: 22 | 23 | name: Tests 24 | 25 | runs-on: macos-latest 26 | 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | node-version: [ 12.x, 17.x ] 31 | 32 | steps: 33 | - name: Use Node.js ${{ matrix.node-version }} 34 | uses: actions/setup-node@v1 35 | with: 36 | node-version: ${{ matrix.node-version }} 37 | 38 | - name: Checkout repository 39 | uses: actions/checkout@v2 40 | 41 | - name: Install dependencies 42 | run: npm ci 43 | 44 | - name: Build TypeScript 45 | run: npm run build 46 | 47 | - name: Run tests 48 | run: TESTNET_ACCOUNT_ID_1=$testnet_account_id_1 TESTNET_PRIVATEKEY_1=$testnet_privatekey_1 TESTNET_ACCOUNT_ID_2=$testnet_account_id_2 TESTNET_PRIVATEKEY_2=$testnet_privatekey_2 PREVIEWNET_ACCOUNT_ID_1=$previewnet_account_id_1 PREVIEWNET_PRIVATEKEY_1=$previewnet_privatekey_1 PREVIEWNET_ACCOUNT_ID_2=$previewnet_account_id_2 PREVIEWNET_PRIVATEKEY_2=$previewnet_privatekey_2 RUN_TEST_ON=$run_test_on npm run test -------------------------------------------------------------------------------- /test/hethers-provider-wrapper.ts: -------------------------------------------------------------------------------- 1 | import 'mocha'; 2 | import {assert} from 'chai'; 3 | import {HethersProviderWrapper} from '../src/internal/hethers-provider-wrapper'; 4 | import {hethers} from '@hashgraph/hethers'; 5 | import {useEnvironment} from './helpers'; 6 | import path from 'path'; 7 | import dotenv from "dotenv"; 8 | dotenv.config({path: path.resolve(__dirname, '../.env')}); 9 | 10 | const test_on = process.env['RUN_TEST_ON']; 11 | // @ts-ignore 12 | const test_on_lowercase = test_on.toLowerCase(); 13 | 14 | 15 | describe('Hethers provider wrapper', function () { 16 | let realProvider: hethers.providers.BaseProvider; 17 | let wrapperProvider: HethersProviderWrapper; 18 | 19 | useEnvironment('hardhat-project', test_on_lowercase); 20 | 21 | beforeEach(function () { 22 | realProvider = new hethers.providers.BaseProvider(test_on_lowercase); 23 | wrapperProvider = new HethersProviderWrapper(this.env.network.provider); 24 | }); 25 | 26 | it('Should return the same as the real provider', async function () { 27 | const accountId = process.env[`${test_on}_ACCOUNT_ID_2`]; 28 | // @ts-ignore 29 | const realProviderResponse = (await realProvider.getBalance(accountId)).toString(); 30 | // @ts-ignore 31 | const wrapperProviderResponse = (await wrapperProvider.getBalance(accountId)).toString(); 32 | 33 | assert.deepEqual(realProviderResponse, wrapperProviderResponse); 34 | }); 35 | 36 | it('Should return the same error', async function () { 37 | try { 38 | await realProvider.getCode('error_please'); 39 | assert.fail('realProvider should have failed'); 40 | } catch (_err) { 41 | const err = _err as Error; 42 | try { 43 | await wrapperProvider.getCode('error_please'); 44 | assert.fail('wrapperProvider should have failed'); 45 | } catch (_err2) { 46 | const err2 = _err2 as Error; 47 | assert.deepEqual(err2.message, err.message); 48 | } 49 | } 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-hethers", 3 | "version": "1.0.2", 4 | "description": "Hardhat plugin for hethers", 5 | "homepage": "https://github.com/LimeChain/hardhat-hethers", 6 | "repository": "github:LimeChain/hardhat-hethers", 7 | "author": "LimeChain", 8 | "license": "MIT", 9 | "main": "internal/index.js", 10 | "types": "internal/index.d.ts", 11 | "keywords": [ 12 | "hedera", 13 | "smart-contracts", 14 | "hardhat", 15 | "hardhat-plugin", 16 | "hethers.js" 17 | ], 18 | "scripts": { 19 | "lint": "yarn node_modules/.bin/prettier --check && yarn eslint", 20 | "lint:fix": "yarn node_modules/.bin/prettier --write && yarn eslint --fix", 21 | "eslint": "node_modules/.bin/eslint 'src/**/*.ts' 'test/**/*.ts'", 22 | "prettier": "node_modules/.bin/prettier \\\"**/*.{js,md,json}\\\"", 23 | "test": "node_modules/.bin/mocha --recursive \\\"test/**/*.ts\\\" --exit", 24 | "build": "npm run clean && node_modules/.bin/tsc --build .", 25 | "clean": "node_modules/.bin/rimraf dist internal types *.{d.ts,js}{,.map} build-test tsconfig.tsbuildinfo" 26 | }, 27 | "files": [ 28 | "dist/src/", 29 | "src/", 30 | "internal/", 31 | "types/", 32 | "*.d.ts", 33 | "*.d.ts.map", 34 | "*.js", 35 | "*.js.map", 36 | "LICENSE", 37 | "README.md" 38 | ], 39 | "devDependencies": { 40 | "@hashgraph/hethers": "^1.1.0", 41 | "@types/chai": "^4.2.0", 42 | "@types/mocha": "^9.0.0", 43 | "@types/node": "^12.0.0", 44 | "@typescript-eslint/eslint-plugin": "4.29.2", 45 | "@typescript-eslint/parser": "4.29.2", 46 | "axios": "^0.26.1", 47 | "chai": "^4.2.0", 48 | "dotenv": "^16.0.0", 49 | "eslint": "^7.29.0", 50 | "eslint-config-prettier": "8.3.0", 51 | "eslint-plugin-import": "2.24.1", 52 | "eslint-plugin-prettier": "3.4.0", 53 | "hardhat": "^2.0.0", 54 | "mocha": "^9.2.2", 55 | "prettier": "2.4.1", 56 | "rimraf": "^3.0.2", 57 | "ts-node": "^8.1.0", 58 | "typescript": "~4.5.2" 59 | }, 60 | "peerDependencies": { 61 | "@hashgraph/hethers": "^1.1.0", 62 | "hardhat": "^2.0.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | /node_modules 3 | 4 | # Compilation output 5 | /build-test/ 6 | /dist 7 | /internal 8 | /types 9 | /*.js 10 | /*.js.map 11 | /*.d.ts 12 | /*.d.ts.map 13 | 14 | # Code coverage artifacts 15 | /coverage 16 | /.nyc_output 17 | 18 | # Below is Github's node gitignore template, 19 | # ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing 20 | 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | 44 | # nyc test coverage 45 | .nyc_output 46 | 47 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 48 | .grunt 49 | 50 | # Bower dependency directory (https://bower.io/) 51 | bower_components 52 | 53 | # node-waf configuration 54 | .lock-wscript 55 | 56 | # Compiled binary addons (https://nodejs.org/api/addons.html) 57 | build/Release 58 | 59 | # Dependency directories 60 | #node_modules/ 61 | jspm_packages/ 62 | 63 | # TypeScript v1 declaration files 64 | typings/ 65 | 66 | # Optional npm cache directory 67 | .npm 68 | 69 | # Optional eslint cache 70 | .eslintcache 71 | 72 | # Optional REPL history 73 | .node_repl_history 74 | 75 | # Output of 'npm pack' 76 | *.tgz 77 | 78 | # Yarn Integrity file 79 | .yarn-integrity 80 | 81 | # parcel-bundler cache (https://parceljs.org/) 82 | .cache 83 | 84 | # next.js build output 85 | .next 86 | 87 | # nuxt.js build output 88 | .nuxt 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | !.eslintrc.js 103 | .idea 104 | 105 | tsconfig.tsbuildinfo 106 | .env -------------------------------------------------------------------------------- /src/internal/type-extensions.ts: -------------------------------------------------------------------------------- 1 | import type {hethers} from "@hashgraph/hethers"; 2 | import {HardhatConfig} from "hardhat/types/config"; 3 | import {HardhatRuntimeEnvironment, Network} from "hardhat/types/runtime"; 4 | import {SignerWithAddress} from "./signers"; 5 | import {Artifact} from "hardhat/types"; 6 | 7 | export interface Libraries { 8 | [libraryName: string]: string; 9 | } 10 | 11 | export interface FactoryOptions { 12 | signer?: hethers.Signer; 13 | libraries?: Libraries; 14 | } 15 | 16 | export declare function getContractFactory( 17 | name: string, 18 | signerOrOptions?: hethers.Signer | FactoryOptions 19 | ): Promise; 20 | export declare function getContractFactory( 21 | abi: any[], 22 | bytecode: hethers.utils.BytesLike, 23 | signer?: hethers.Signer 24 | ): Promise; 25 | 26 | export interface HederaAccount { 27 | account?: string; 28 | address?: string; 29 | alias?: string; 30 | privateKey: string; 31 | } 32 | 33 | export interface HederaNodeConfig { 34 | url: string; 35 | nodeId: string; 36 | } 37 | 38 | export interface HederaNetwork { 39 | accounts?: Array; 40 | nodeId?: string; 41 | consensusNodes?: Array; 42 | mirrorNodeUrl?: string; 43 | chainId?: number; 44 | } 45 | 46 | export interface HederaNetworks { 47 | [name: string]: HederaNetwork 48 | } 49 | 50 | export interface HederaConfig { 51 | gasLimit: number; 52 | networks: HederaNetworks; 53 | } 54 | 55 | export interface HederaHardhatConfig extends HardhatConfig { 56 | hedera?: HederaConfig; 57 | networks: any; 58 | } 59 | 60 | interface HederaNetworkInterface extends Network { 61 | provider: any; 62 | } 63 | 64 | type HethersT = typeof hethers; 65 | 66 | interface HethersTExtended extends HethersT { 67 | provider: any, 68 | 69 | getSigners(hre: HederaHardhatRuntimeEnvironment): Promise; 70 | 71 | getSigner(hre: HederaHardhatRuntimeEnvironment, identifier: any): Promise; 72 | 73 | getContractFactory( 74 | hre: HederaHardhatRuntimeEnvironment, 75 | nameOrAbi: string | any[], 76 | bytecodeOrFactoryOptions?: 77 | | (hethers.Signer | FactoryOptions) 78 | | hethers.utils.BytesLike, 79 | signer?: hethers.Signer 80 | ): Promise; 81 | 82 | getContractFactoryFromArtifact( 83 | hre: HardhatRuntimeEnvironment, 84 | artifact: Artifact, 85 | signerOrOptions?: hethers.Signer | FactoryOptions 86 | ): Promise; 87 | 88 | getContractAt( 89 | hre: HederaHardhatRuntimeEnvironment, 90 | nameOrAbi: string | any[], 91 | address: string, 92 | signer?: hethers.Signer 93 | ): Promise; 94 | 95 | getContractAtFromArtifact( 96 | hre: HederaHardhatRuntimeEnvironment, 97 | artifact: Artifact, 98 | address: string, 99 | signer?: hethers.Signer 100 | ): Promise; 101 | } 102 | 103 | export interface HederaHardhatRuntimeEnvironment extends HardhatRuntimeEnvironment { 104 | hethers?: HethersTExtended; 105 | config: HederaHardhatConfig; 106 | network: HederaNetworkInterface; 107 | } -------------------------------------------------------------------------------- /src/internal/updatable-target-proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a read-only proxy that just forwards everything to a target, 3 | * and a function that can be used to change that underlying target 4 | */ 5 | export function createUpdatableTargetProxy( 6 | initialTarget: T 7 | ): { 8 | proxy: T; 9 | setTarget: (target: T) => void; 10 | } { 11 | const targetObject = { 12 | target: initialTarget, 13 | }; 14 | 15 | let isExtensible = Object.isExtensible(initialTarget); 16 | 17 | // @ts-ignore 18 | const handler: Required> = { 19 | // these two functions are implemented because of the Required type 20 | apply(_, _thisArg, _argArray) { 21 | throw new Error( 22 | "cannot be implemented because the target is not a function" 23 | ); 24 | }, 25 | 26 | construct(_, _argArray, _newTarget) { 27 | throw new Error( 28 | "cannot be implemented because the target is not a function" 29 | ); 30 | }, 31 | 32 | defineProperty(_, property, _descriptor) { 33 | throw new Error( 34 | `cannot define property ${String(property)} in read-only proxy` 35 | ); 36 | }, 37 | 38 | deleteProperty(_, property) { 39 | throw new Error( 40 | `cannot delete property ${String(property)} in read-only proxy` 41 | ); 42 | }, 43 | 44 | get(_, property, receiver) { 45 | const result = Reflect.get(targetObject.target, property, receiver); 46 | 47 | if (result instanceof Function) { 48 | return result.bind(targetObject.target); 49 | } 50 | 51 | return result; 52 | }, 53 | 54 | getOwnPropertyDescriptor(_, property) { 55 | const descriptor = Reflect.getOwnPropertyDescriptor( 56 | targetObject.target, 57 | property 58 | ); 59 | 60 | if (descriptor !== undefined) { 61 | Object.defineProperty(targetObject.target, property, descriptor); 62 | } 63 | 64 | return descriptor; 65 | }, 66 | 67 | getPrototypeOf(_) { 68 | return Reflect.getPrototypeOf(targetObject.target); 69 | }, 70 | 71 | has(_, property) { 72 | return Reflect.has(targetObject.target, property); 73 | }, 74 | 75 | isExtensible(_) { 76 | // we need to return the extensibility value of the original target 77 | return isExtensible; 78 | }, 79 | 80 | ownKeys(_) { 81 | return Reflect.ownKeys(targetObject.target); 82 | }, 83 | 84 | preventExtensions(_) { 85 | isExtensible = false; 86 | return Reflect.preventExtensions(targetObject.target); 87 | }, 88 | 89 | set(_, property, _value, _receiver) { 90 | throw new Error( 91 | `cannot set property ${String(property)} in read-only proxy` 92 | ); 93 | }, 94 | 95 | setPrototypeOf(_, _prototype) { 96 | throw new Error("cannot change the prototype in read-only proxy"); 97 | }, 98 | }; 99 | 100 | const proxy: T = new Proxy(initialTarget, handler); 101 | 102 | const setTarget = (newTarget: T) => { 103 | targetObject.target = newTarget; 104 | }; 105 | 106 | return {proxy, setTarget}; 107 | } 108 | -------------------------------------------------------------------------------- /src/internal/signers.ts: -------------------------------------------------------------------------------- 1 | import {hethers} from "@hashgraph/hethers"; 2 | import {HederaAccount} from "./type-extensions"; 3 | import {Deferrable} from "@ethersproject/properties"; 4 | import {TransactionRequest} from "@hethers/abstract-provider"; 5 | import {NomicLabsHardhatPluginError} from "hardhat/plugins"; 6 | 7 | const pluginName = "hardhat-hethers"; 8 | 9 | export class SignerWithAddress extends hethers.Wallet { 10 | // @ts-ignore 11 | address: string; 12 | 13 | private static populateDefaultGasLimit(tx: TransactionRequest): TransactionRequest { 14 | // do not force add gasLimit, if the user wants to execute a simple crypto transfer 15 | if (tx.to && tx.value && Object.keys(tx).length == 2) { 16 | return tx; 17 | } 18 | 19 | if (!tx.gasLimit) { 20 | const env = require('hardhat'); 21 | if (!env.config.hedera.gasLimit) { 22 | throw new NomicLabsHardhatPluginError( 23 | pluginName, 24 | `No default gas limit found. Please specify a default 'gasLimit' value in your hardhat.config.js: > config > hedera > gasLimit` 25 | ); 26 | } 27 | 28 | tx.gasLimit = env.config.hedera.gasLimit; 29 | } 30 | return tx; 31 | } 32 | 33 | public static async create(signer: hethers.Wallet) { 34 | // @ts-ignore 35 | let signerWithAddress = await new SignerWithAddress(signer._signingKey(), signer); 36 | // @ts-ignore 37 | signerWithAddress.address = signer.address; 38 | return signerWithAddress; 39 | } 40 | 41 | constructor( 42 | public readonly identity: HederaAccount, 43 | private readonly _signer: hethers.Signer 44 | ) { 45 | // @ts-ignore 46 | super(identity, _signer.provider); 47 | } 48 | 49 | public async getAddress(): Promise { 50 | return this._signer.getAddress(); 51 | } 52 | 53 | public signMessage(message: string | hethers.utils.Bytes): Promise { 54 | return this._signer.signMessage(message); 55 | } 56 | 57 | public signTransaction(transaction: hethers.providers.TransactionRequest): Promise { 58 | transaction = SignerWithAddress.populateDefaultGasLimit(transaction); 59 | return this._signer.signTransaction(transaction); 60 | } 61 | 62 | public sendTransaction(transaction: hethers.providers.TransactionRequest): Promise { 63 | transaction = SignerWithAddress.populateDefaultGasLimit(transaction); 64 | return this._signer.sendTransaction(transaction); 65 | } 66 | 67 | // @ts-ignore 68 | public connect(provider: hethers.providers.BaseProvider): SignerWithAddress { 69 | // @ts-ignore 70 | return new SignerWithAddress(this.identity, this._signer.connect(provider)); 71 | } 72 | 73 | public createAccount(pubKey: hethers.utils.BytesLike, initialBalance?: BigInt): Promise { 74 | return this._signer.createAccount(pubKey, initialBalance); 75 | } 76 | 77 | public toJSON() { 78 | return ``; 79 | } 80 | 81 | public async call(txRequest: Deferrable): Promise { 82 | txRequest = SignerWithAddress.populateDefaultGasLimit(txRequest); 83 | if (!txRequest.to) { 84 | throw new NomicLabsHardhatPluginError( 85 | pluginName, 86 | `The transaction is missing a required field: 'to'` 87 | ); 88 | } 89 | return this._signer.call(txRequest); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /test/updatable-target-proxy.ts: -------------------------------------------------------------------------------- 1 | import 'mocha'; 2 | import {assert} from 'chai'; 3 | import {createUpdatableTargetProxy} from '../src/internal/updatable-target-proxy'; 4 | 5 | describe('Updatable target proxy', function () { 6 | it('should proxy properties', function () { 7 | const o: any = { 8 | a: 1, 9 | getA() { 10 | return this.a; 11 | }, 12 | b: {}, 13 | getB() { 14 | return this.b; 15 | }, 16 | }; 17 | 18 | const {proxy} = createUpdatableTargetProxy(o); 19 | 20 | assert.equal(proxy.a, 1); 21 | assert.equal(proxy.getA(), 1); 22 | assert.equal(proxy.b, o.b); 23 | assert.equal(proxy.getB(), o.b); 24 | }); 25 | 26 | it('should let set a new target', function () { 27 | const o1: any = { 28 | a: 1, 29 | getA() { 30 | return this.a; 31 | }, 32 | b: {}, 33 | getB() { 34 | return this.b; 35 | }, 36 | }; 37 | 38 | const o2: any = { 39 | a: 2, 40 | getA() { 41 | return this.a; 42 | }, 43 | b: {}, 44 | getB() { 45 | return this.b; 46 | }, 47 | }; 48 | 49 | const {proxy, setTarget} = createUpdatableTargetProxy(o1); 50 | 51 | assert.equal(proxy.a, 1); 52 | 53 | setTarget(o2); 54 | 55 | assert.equal(proxy.a, 2); 56 | assert.equal(proxy.getA(), 2); 57 | assert.equal(proxy.b, o2.b); 58 | assert.equal(proxy.getB(), o2.b); 59 | }); 60 | 61 | it('shouldn`t let you modify the proxied object', function () { 62 | const o: any = { 63 | a: 1, 64 | }; 65 | 66 | const {proxy} = createUpdatableTargetProxy(o); 67 | 68 | assert.throws(() => { 69 | proxy.a = 2; 70 | }); 71 | assert.throws(() => { 72 | delete proxy.a; 73 | }); 74 | assert.throws(() => { 75 | Object.defineProperty(proxy, 'b', {}); 76 | }); 77 | assert.throws(() => { 78 | Object.setPrototypeOf(proxy, {}); 79 | }); 80 | }); 81 | 82 | it('should let you call methods that modify the object', function () { 83 | const o = { 84 | a: 1, 85 | inc() { 86 | this.a++; 87 | }, 88 | }; 89 | 90 | const {proxy} = createUpdatableTargetProxy(o); 91 | 92 | assert.equal(proxy.a, 1); 93 | proxy.inc(); 94 | assert.equal(proxy.a, 2); 95 | }); 96 | 97 | it('should trap getOwnPropertyDescriptor correctly', () => { 98 | const o = {a: 1}; 99 | const {proxy, setTarget} = createUpdatableTargetProxy(o); 100 | 101 | assert.deepEqual(Object.getOwnPropertyDescriptor(proxy, 'a'), { 102 | value: 1, 103 | writable: true, 104 | enumerable: true, 105 | configurable: true, 106 | }); 107 | 108 | const o2 = {a: 2, b: 3}; 109 | setTarget(o2); 110 | 111 | assert.deepEqual(Object.getOwnPropertyDescriptor(proxy, 'a'), { 112 | value: 2, 113 | writable: true, 114 | enumerable: true, 115 | configurable: true, 116 | }); 117 | assert.deepEqual(Object.getOwnPropertyDescriptor(proxy, 'b'), { 118 | value: 3, 119 | writable: true, 120 | enumerable: true, 121 | configurable: true, 122 | }); 123 | }); 124 | 125 | it('should trap getPrototypeOf correctly', () => { 126 | const proto = {}; 127 | const o = Object.create(proto); 128 | 129 | const {proxy, setTarget} = createUpdatableTargetProxy(o); 130 | 131 | assert.equal(Object.getPrototypeOf(proxy), proto); 132 | 133 | const proto2 = {}; 134 | const o2 = Object.create(proto2); 135 | 136 | setTarget(o2); 137 | assert.equal(Object.getPrototypeOf(proxy), proto2); 138 | }); 139 | 140 | it('should trap has correctly', () => { 141 | const proto = {a: 1}; 142 | const o = Object.create(proto); 143 | o.b = 2; 144 | 145 | const {proxy, setTarget} = createUpdatableTargetProxy(o); 146 | 147 | assert.isTrue('a' in proxy); 148 | assert.isTrue('b' in proxy); 149 | assert.isFalse('c' in proxy); 150 | 151 | const proto2 = {a: 2}; 152 | const o2 = Object.create(proto2); 153 | o2.b = 4; 154 | o2.c = 6; 155 | 156 | setTarget(o2); 157 | assert.isTrue('a' in proxy); 158 | assert.isTrue('b' in proxy); 159 | assert.isTrue('c' in proxy); 160 | assert.isFalse('d' in proxy); 161 | }); 162 | 163 | it('should return isExtensible correctly', () => { 164 | const o: any = {}; 165 | Object.preventExtensions(o); 166 | 167 | const {proxy, setTarget} = createUpdatableTargetProxy(o); 168 | 169 | assert.isFalse(Object.isExtensible(proxy)); 170 | 171 | // if the proxy is initially not extensible, then it can't be made 172 | // extensible afterwards 173 | setTarget({}); 174 | assert.isFalse(Object.isExtensible(proxy)); 175 | }); 176 | 177 | it('should trap ownKeys correctly', () => { 178 | const proto = {a: 1}; 179 | const o: any = Object.create(proto); 180 | o.b = 1; 181 | 182 | const {proxy, setTarget} = createUpdatableTargetProxy(o); 183 | assert.deepEqual(Object.getOwnPropertyNames(proxy), ['b']); 184 | 185 | const proto2 = {c: 1}; 186 | const o2: any = Object.create(proto2); 187 | o2.d = 1; 188 | setTarget(o2); 189 | assert.deepEqual(Object.getOwnPropertyNames(proxy), ['d']); 190 | }); 191 | 192 | it('should trap preventExtensions correctly', () => { 193 | const o: any = {}; 194 | 195 | const {proxy} = createUpdatableTargetProxy(o); 196 | assert.isTrue(Object.isExtensible(proxy)); 197 | 198 | Object.preventExtensions(proxy); 199 | assert.isFalse(Object.isExtensible(proxy)); 200 | }); 201 | }); 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/hardhat-hethers.svg)](https://www.npmjs.com/package/hardhat-hethers) [![hardhat](https://hardhat.org/buidler-plugin-badge.svg?1)](https://hardhat.org) 2 | 3 | 4 | --- 5 | **Deprecated!** This package has been deprecated, please use [this one](https://www.npmjs.com/package/@hashgraph/hardhat-hethers) instead 6 | --- 7 | 8 | 9 | 10 | # hardhat-hethers 11 | 12 | [Hardhat](https://hardhat.org) plugin for integration with [hethers.js](https://github.com/hashgraph/hethers.js). 13 | 14 | ## What 15 | 16 | This plugin brings to Hardhat the Hedera library `hethers.js`, which allows you to interact with the Hedera hashgraph in a simple way. 17 | 18 | ## Installation 19 | 20 | ```bash 21 | npm install --save-dev 'hardhat-hethers' 22 | ``` 23 | 24 | And add the following statement to your `hardhat.config.js`: 25 | 26 | ```js 27 | require("hardhat-hethers"); 28 | ``` 29 | 30 | Add network configuration in `hardhat.config.js`: 31 | 32 | ```js 33 | module.exports = { 34 | defaultNetwork: 'testnet', // The selected default network. It has to match the name of one of the configured networks. 35 | hedera: { 36 | gasLimit: 300000, // Default gas limit. It is added to every contract transaction, but can be overwritten if required. 37 | networks: { 38 | testnet: { // The name of the network, e.g. mainnet, testnet, previewnet, customNetwork 39 | accounts: [ // An array of predefined Externally Owned Accounts 40 | { 41 | "account": '0.0.123', 42 | "privateKey": '0x...' 43 | }, 44 | ... 45 | ] 46 | }, 47 | previewnet: { 48 | accounts: [ 49 | { 50 | "account": '0.0.123', 51 | "privateKey": '0x...' 52 | }, 53 | ... 54 | ] 55 | }, 56 | ... 57 | }, 58 | // Custom networks require additional configuration - for conesensusNodes and mirrorNodeUrl 59 | // The following is an integration example for the local-hedera package 60 | customNetwork: { 61 | consensusNodes: [ 62 | { 63 | url: '127.0.0.1:50211', 64 | nodeId: '0.0.3' 65 | } 66 | ], 67 | mirrorNodeUrl: 'http://127.0.0.1:5551', 68 | chainId: 0, 69 | accounts: [ 70 | { 71 | 'account': '0.0.1001', 72 | 'privateKey': '0x7f109a9e3b0d8ecfba9cc23a3614433ce0fa7ddcc80f2a8f10b222179a5a80d6' 73 | }, 74 | { 75 | 'account': '0.0.1002', 76 | 'privateKey': '0x6ec1f2e7d126a74a1d2ff9e1c5d90b92378c725e506651ff8bb8616a5c724628' 77 | }, 78 | ] 79 | } 80 | } 81 | }; 82 | ``` 83 | 84 | Read more about Externally Owned Accounts [here](https://docs.hedera.com/hethers/application-programming-interface/signers#externallyownedaccount). 85 | 86 | 87 | ## Tasks 88 | 89 | This plugin creates no additional tasks. 90 | 91 | ## Environment extensions 92 | 93 | This plugins adds an `hethers` object to the Hardhat Runtime Environment. 94 | 95 | This object has the [same API](https://docs.hedera.com/hethers/) as `hethers.js`, with some extra Hardhat-specific functionality. 96 | 97 | ### Provider object 98 | 99 | A `provider` field is added to `hethers`, which is an [`hethers.providers.BaseProvider`](https://docs.hedera.com/hethers/application-programming-interface/providers/provider/base-provider) automatically connected to the selected network. 100 | 101 | ### Helpers 102 | 103 | These helpers are added to the `hethers` object: 104 | 105 | #### Interfaces 106 | ```typescript 107 | interface Libraries { 108 | [libraryName: string]: string; 109 | } 110 | 111 | interface FactoryOptions { 112 | signer?: hethers.Signer; 113 | libraries?: Libraries; 114 | } 115 | ``` 116 | 117 | #### Functions 118 | - `function getSigners() => Promise;` 119 | ```typescript 120 | const signers = await hre.hethers.getSingers(); 121 | ``` 122 | 123 | - `function getSigner(identifier: any) => Promise;` 124 | ```typescript 125 | const signer = await hre.hethers.getSigner({ 126 | "account": "0.0.123", 127 | "privateKey": "0x..." 128 | }); 129 | ``` 130 | 131 | - `function getContractFactory(name: string, signer?: hethers.Signer): Promise;` 132 | ```typescript 133 | const contractFactoryWithDefaultSigner = await hre.hethers.getContractFactory('Greeter'); 134 | const signer = (await hre.getSigners())[1]; 135 | 136 | const contractFactoryWithCustomSigner = await hre.hethers.getContractFactory('Greeter', signer); 137 | ``` 138 | 139 | - `function getContractFactory(name: string, factoryOptions: FactoryOptions): Promise;` 140 | ```typescript 141 | const libraryFactory = await hre.hethers.getContractFactory("contracts/TestContractLib.sol:TestLibrary"); 142 | const library = await libraryFactory.deploy(); 143 | 144 | const contract = await hre.hethers.getContractFactory("Greeter", { 145 | libraries: { 146 | "contracts/Greeter.sol:TestLibrary": library.address 147 | } 148 | }); 149 | ``` 150 | 151 | - `function getContractFactory(abi: any[], bytecode: hethers.utils.BytesLike, signer?: hethers.Signer): Promise;` 152 | ```typescript 153 | const greeterArtifact = await hre.artifacts.readArtifact("Greeter"); 154 | 155 | const contract = await hre.hethers.getContractFactory(greeterArtifact.abi, greeterArtifact.bytecode); 156 | ``` 157 | 158 | - `function getContractAt(name: string, address: string, signer?: hethers.Signer): Promise;` 159 | ```typescript 160 | const Greeter = await hre.hethers.getContractFactory("Greeter"); 161 | const deployedGreeter = await Greeter.deploy(); 162 | 163 | const contract = await hre.hethers.getContractAt("Greeter", deployedGreeter.address); 164 | ``` 165 | 166 | - `function getContractAt(abi: any[], address: string, signer?: hethers.Signer): Promise;` 167 | ```typescript 168 | const greeterArtifact = await hre.artifacts.readArtifact("Greeter"); 169 | 170 | const contract = await hre.hethers.getContractAt(greeterArtifact.abi, deployedGreeter.address); 171 | ``` 172 | 173 | - `function getContractFactoryFromArtifact(artifact: Artifact, signer?: hethers.Signer): Promise;` 174 | ```typescript 175 | const greeterArtifact = await hre.artifacts.readArtifact("Greeter"); 176 | 177 | const contractFactoryFromArtifact = await hre.hethers.getContractFactoryFromArtifact(greeterArtifact); 178 | ``` 179 | 180 | - `function getContractFactoryFromArtifact(artifact: Artifact, factoryOptions: FactoryOptions): Promise;` 181 | ```typescript 182 | const greeterArtifact = await hre.artifacts.readArtifact("Greeter"); 183 | const libraryFactory = await hre.hethers.getContractFactory( 184 | "contracts/TestContractLib.sol:TestLibrary" 185 | ); 186 | const library = await libraryFactory.deploy(); 187 | 188 | const contract = await hre.hethers.getContractFactory(greeterArtifact, { 189 | libraries: { 190 | "contracts/TestContractLib.sol:TestLibrary": library.address 191 | } 192 | }); 193 | ``` 194 | 195 | - `function getContractAtFromArtifact(artifact: Artifact, address: string, signer?: hethers.Signer): Promise;` 196 | ```typescript 197 | const Greeter = await hre.hethers.getContractFactory("Greeter"); 198 | const deployedGreeter = await Greeter.deploy(); 199 | const greeterArtifact = await hre.artifacts.readArtifact("Greeter"); 200 | 201 | const contract = await hre.getContractAtFromArtifact(greeterArtifact, deployedGreeter.address); 202 | ``` 203 | 204 | The [`Contract's`](https://docs.hedera.com/hethers/application-programming-interface/contract-interaction/contract) and [`ContractFactory's`](https://docs.hedera.com/hethers/application-programming-interface/contract-interaction/contractfactory) returned by these helpers are connected to the first [signer](https://docs.hedera.com/hethers/application-programming-interface/signers#wallet) returned by `getSigners` by default. 205 | 206 | ## Usage 207 | 208 | There are no additional steps you need to take for this plugin to work. 209 | 210 | Install it and access hethers through the Hardhat Runtime Environment anywhere you need it (tasks, scripts, tests, etc). For example, in your `hardhat.config.js`: 211 | 212 | ```typescript 213 | require("hardhat-hethers"); 214 | 215 | // task action function receives the Hardhat Runtime Environment as second argument 216 | task('getBalance', 'Prints the the balance of "0.0.29631749"', async (_, {hethers}) => { 217 | const balance = (await hethers.provider.getBalance('0.0.29631749')).toString(); 218 | c onsole.log(`Balance of "0.0.29631749": ${balance} tinybars`); 219 | }); 220 | 221 | module.exports = {}; 222 | ``` 223 | 224 | And then run `npx hardhat getBalance` to try it. 225 | 226 | Read the documentation on the [Hardhat Runtime Environment](https://hardhat.org/advanced/hardhat-runtime-environment.html) to learn how to access the HRE in different ways to use hethers.js from anywhere the HRE is accessible. 227 | 228 | ### Library linking 229 | 230 | Some contracts need to be linked with libraries before they are deployed. You can pass the addresses of their libraries to the `getContractFactory` function with an object like this: 231 | 232 | ```typescript 233 | const contractFactory = await this.env.hethers.getContractFactory("Example", { 234 | libraries: { 235 | ExampleLib: "0x...", 236 | }, 237 | }); 238 | ``` 239 | 240 | This allows you to create a contract factory for the `Example` contract and link its `ExampleLib` library references to the address `"0x..."`. 241 | 242 | To create a contract factory, all libraries must be linked. An error will be thrown informing you of any missing library. 243 | 244 | ## Troubleshooting 245 | 246 | ### Events are not being emitted 247 | 248 | Hethers.js polls the network to check if some event was emitted (except when a `WebSocketProvider` is used; see below). This polling is done every 4 seconds. If you have a script or test that is not emitting an event, it's likely that the execution is finishing before the event is detected by the polling mechanism. 249 | 250 | If you are connecting to a Hardhat node using a `WebSocketProvider`, events should be emitted immediately. But keep in mind that you'll have to create this provider manually, since Hardhat only supports configuring networks via http. That is, you can't add a `localhost` network with a URL like `ws://localhost:8545`. 251 | -------------------------------------------------------------------------------- /src/internal/helpers.ts: -------------------------------------------------------------------------------- 1 | import {NomicLabsHardhatPluginError} from "hardhat/plugins"; 2 | import {Artifact, HardhatRuntimeEnvironment,} from "hardhat/types"; 3 | import type {SignerWithAddress} from "./signers"; 4 | import {HederaHardhatRuntimeEnvironment, HederaNodeConfig} from "./type-extensions"; 5 | import type {FactoryOptions, Libraries} from "./type-extensions"; 6 | import {hethers} from "@hashgraph/hethers"; 7 | 8 | interface Link { 9 | sourceName: string; 10 | libraryName: string; 11 | address: string; 12 | } 13 | 14 | const pluginName = "hardhat-hethers"; 15 | 16 | function isArtifact(artifact: any): artifact is Artifact { 17 | const { 18 | contractName, 19 | sourceName, 20 | abi, 21 | bytecode, 22 | deployedBytecode, 23 | linkReferences, 24 | deployedLinkReferences, 25 | } = artifact; 26 | 27 | return ( 28 | typeof contractName === "string" && 29 | typeof sourceName === "string" && 30 | Array.isArray(abi) && 31 | typeof bytecode === "string" && 32 | typeof deployedBytecode === "string" && 33 | linkReferences !== undefined && 34 | deployedLinkReferences !== undefined 35 | ); 36 | } 37 | 38 | export function getInitialHederaProvider(hre: HederaHardhatRuntimeEnvironment): hethers.providers.BaseProvider { 39 | const networkName = hre.hardhatArguments.network || hre.config.defaultNetwork; 40 | if (['mainnet', 'testnet', 'previewnet'].indexOf(networkName.toLocaleLowerCase()) > -1) { 41 | return hethers.getDefaultProvider(networkName); 42 | } 43 | 44 | const {consensusNodes, mirrorNodeUrl, chainId} = hre.config.networks[networkName]; 45 | if (consensusNodes?.length && mirrorNodeUrl && chainId != undefined && consensusNodes.length) { 46 | let cnNetworkConfig: { [url: string]: string } = {}; 47 | consensusNodes.forEach(function (obj: HederaNodeConfig) { 48 | cnNetworkConfig[obj.url] = obj.nodeId; 49 | }); 50 | 51 | let provider = new hethers.providers.BaseProvider({ 52 | network: cnNetworkConfig, 53 | mirrorNodeUrl: mirrorNodeUrl, 54 | }); 55 | provider._network.name = networkName; 56 | provider._network.chainId = chainId; 57 | 58 | return provider; 59 | } 60 | 61 | // TODO: implement hardhat network 62 | // currently we don't support `hardhat` network, so just get the testnet as fallback 63 | return hethers.getDefaultProvider('testnet'); 64 | } 65 | 66 | export async function getSigners( 67 | hre: HardhatRuntimeEnvironment 68 | ): Promise { 69 | // @ts-ignore 70 | const accounts = hre.network.provider.listAccounts(); 71 | 72 | return await Promise.all( 73 | accounts.map((identifier: any) => getSigner(hre, identifier)) 74 | ); 75 | } 76 | 77 | export async function getSigner( 78 | hre: HardhatRuntimeEnvironment, 79 | identifier: any 80 | ): Promise { 81 | const {SignerWithAddress: SignerWithAddressImpl} = await import("./signers"); 82 | 83 | // @ts-ignore 84 | const signer = hre.network.provider.getSigner(identifier); 85 | 86 | return await SignerWithAddressImpl.create(signer); 87 | } 88 | 89 | 90 | export function getContractFactory( 91 | hre: HederaHardhatRuntimeEnvironment, 92 | name: string, 93 | signerOrOptions?: hethers.Signer | FactoryOptions 94 | ): Promise; 95 | 96 | export function getContractFactory( 97 | hre: HederaHardhatRuntimeEnvironment, 98 | abi: any[], 99 | bytecode: hethers.utils.BytesLike, 100 | signer?: hethers.Signer 101 | ): Promise; 102 | 103 | export async function getContractFactory( 104 | hre: HederaHardhatRuntimeEnvironment, 105 | nameOrAbi: string | any[], 106 | bytecodeOrFactoryOptions?: 107 | | (hethers.Signer | FactoryOptions) 108 | | hethers.utils.BytesLike, 109 | signer?: hethers.Signer 110 | ) { 111 | if (typeof nameOrAbi === "string") { 112 | const artifact = await hre.artifacts.readArtifact(nameOrAbi); 113 | return getContractFactoryFromArtifact( 114 | hre, 115 | artifact, 116 | bytecodeOrFactoryOptions as hethers.Signer | FactoryOptions | undefined 117 | ); 118 | } 119 | 120 | return getContractFactoryByAbiAndBytecode( 121 | hre, 122 | nameOrAbi, 123 | bytecodeOrFactoryOptions as hethers.utils.BytesLike, 124 | signer 125 | ); 126 | } 127 | 128 | function isFactoryOptions( 129 | signerOrOptions?: hethers.Signer | FactoryOptions 130 | ): signerOrOptions is FactoryOptions { 131 | const {Signer} = require("@hashgraph/hethers") as typeof hethers; 132 | const SignerWithAddressObj = require("./signers").SignerWithAddress; 133 | // @ts-ignore 134 | if (signerOrOptions === undefined || signerOrOptions instanceof Signer || signerOrOptions instanceof SignerWithAddressObj) { 135 | return false; 136 | } 137 | 138 | return true; 139 | } 140 | 141 | export async function getContractFactoryFromArtifact( 142 | hre: HederaHardhatRuntimeEnvironment, 143 | artifact: Artifact, 144 | signerOrOptions?: hethers.Signer | FactoryOptions 145 | ) { 146 | let libraries: Libraries = {}; 147 | let signer: hethers.Signer | undefined; 148 | 149 | if (!isArtifact(artifact)) { 150 | throw new NomicLabsHardhatPluginError( 151 | pluginName, 152 | `You are trying to create a contract factory from an artifact, but you have not passed a valid artifact parameter.` 153 | ); 154 | } 155 | 156 | if (isFactoryOptions(signerOrOptions)) { 157 | signer = signerOrOptions.signer; 158 | libraries = signerOrOptions.libraries ?? {}; 159 | } else { 160 | signer = signerOrOptions; 161 | } 162 | 163 | if (artifact.bytecode === "0x") { 164 | throw new NomicLabsHardhatPluginError( 165 | pluginName, 166 | `You are trying to create a contract factory for the contract ${artifact.contractName}, which is abstract and can't be deployed. 167 | If you want to call a contract using ${artifact.contractName} as its interface use the "getContractAt" function instead.` 168 | ); 169 | } 170 | 171 | const linkedBytecode = await collectLibrariesAndLink(artifact, libraries); 172 | 173 | return getContractFactoryByAbiAndBytecode( 174 | hre, 175 | artifact.abi, 176 | linkedBytecode, 177 | signer 178 | ); 179 | } 180 | 181 | async function collectLibrariesAndLink( 182 | artifact: Artifact, 183 | libraries: Libraries 184 | ) { 185 | const {utils} = require("@hashgraph/hethers") as typeof hethers; 186 | 187 | const neededLibraries: Array<{ 188 | sourceName: string; 189 | libName: string; 190 | }> = []; 191 | for (const [sourceName, sourceLibraries] of Object.entries( 192 | artifact.linkReferences 193 | )) { 194 | for (const libName of Object.keys(sourceLibraries)) { 195 | neededLibraries.push({sourceName, libName}); 196 | } 197 | } 198 | 199 | const linksToApply: Map = new Map(); 200 | for (const [linkedLibraryName, linkedLibraryAddress] of Object.entries( 201 | libraries 202 | )) { 203 | if (!utils.isAddress(linkedLibraryAddress)) { 204 | throw new NomicLabsHardhatPluginError( 205 | pluginName, 206 | `You tried to link the contract ${artifact.contractName} with the library ${linkedLibraryName}, but provided this invalid address: ${linkedLibraryAddress}` 207 | ); 208 | } 209 | 210 | const matchingNeededLibraries = neededLibraries.filter((lib) => { 211 | return ( 212 | lib.libName === linkedLibraryName || 213 | `${lib.sourceName}:${lib.libName}` === linkedLibraryName 214 | ); 215 | }); 216 | 217 | if (matchingNeededLibraries.length === 0) { 218 | let detailedMessage: string; 219 | if (neededLibraries.length > 0) { 220 | const libraryFQNames = neededLibraries 221 | .map((lib) => `${lib.sourceName}:${lib.libName}`) 222 | .map((x) => `* ${x}`) 223 | .join("\n"); 224 | detailedMessage = `The libraries needed are: 225 | ${libraryFQNames}`; 226 | } else { 227 | detailedMessage = "This contract doesn't need linking any libraries."; 228 | } 229 | throw new NomicLabsHardhatPluginError( 230 | pluginName, 231 | `You tried to link the contract ${artifact.contractName} with ${linkedLibraryName}, which is not one of its libraries. 232 | ${detailedMessage}` 233 | ); 234 | } 235 | 236 | if (matchingNeededLibraries.length > 1) { 237 | const matchingNeededLibrariesFQNs = matchingNeededLibraries 238 | .map(({sourceName, libName}) => `${sourceName}:${libName}`) 239 | .map((x) => `* ${x}`) 240 | .join("\n"); 241 | throw new NomicLabsHardhatPluginError( 242 | pluginName, 243 | `The library name ${linkedLibraryName} is ambiguous for the contract ${artifact.contractName}. 244 | It may resolve to one of the following libraries: 245 | ${matchingNeededLibrariesFQNs} 246 | 247 | To fix this, choose one of these fully qualified library names and replace where appropriate.` 248 | ); 249 | } 250 | 251 | const [neededLibrary] = matchingNeededLibraries; 252 | 253 | const neededLibraryFQN = `${neededLibrary.sourceName}:${neededLibrary.libName}`; 254 | 255 | // The only way for this library to be already mapped is 256 | // for it to be given twice in the libraries user input: 257 | // once as a library name and another as a fully qualified library name. 258 | if (linksToApply.has(neededLibraryFQN)) { 259 | throw new NomicLabsHardhatPluginError( 260 | pluginName, 261 | `The library names ${neededLibrary.libName} and ${neededLibraryFQN} refer to the same library and were given as two separate library links. 262 | Remove one of them and review your library links before proceeding.` 263 | ); 264 | } 265 | 266 | linksToApply.set(neededLibraryFQN, { 267 | sourceName: neededLibrary.sourceName, 268 | libraryName: neededLibrary.libName, 269 | address: linkedLibraryAddress, 270 | }); 271 | } 272 | 273 | if (linksToApply.size < neededLibraries.length) { 274 | const missingLibraries = neededLibraries 275 | .map((lib) => `${lib.sourceName}:${lib.libName}`) 276 | .filter((libFQName) => !linksToApply.has(libFQName)) 277 | .map((x) => `* ${x}`) 278 | .join("\n"); 279 | 280 | throw new NomicLabsHardhatPluginError( 281 | pluginName, 282 | `The contract ${artifact.contractName} is missing links for the following libraries: 283 | ${missingLibraries} 284 | 285 | Learn more about linking contracts at https://hardhat.org/plugins/nomiclabs-hardhat-hethers.html#library-linking 286 | ` 287 | ); 288 | } 289 | 290 | return linkBytecode(artifact, [...linksToApply.values()]); 291 | } 292 | 293 | // @ts-ignore 294 | function defaultNthArgument(fn, n, thisObj, defaultObj) { 295 | return function (...args: any) { 296 | let receivedArgs = args.length; 297 | 298 | // Check if the last argument is an options object 299 | if (typeof args[receivedArgs - 1] === 'object') { 300 | // don't count it 301 | receivedArgs--; 302 | } 303 | 304 | if (receivedArgs !== n) { 305 | // call the function without the default gas limit appended to 306 | // force it to throw a MISSING_ARGUMENT or an UNEXPECTED_ARGUMENT error 307 | return fn.call(thisObj, ...args.slice(0, receivedArgs)); 308 | } 309 | 310 | let overwritten = args[n] || {}; 311 | overwritten = Object.assign({}, defaultObj, overwritten); 312 | return fn.call(thisObj, ...args.slice(0, n), overwritten); 313 | }; 314 | } 315 | 316 | async function getContractFactoryByAbiAndBytecode( 317 | hre: HederaHardhatRuntimeEnvironment, 318 | abi: any[], 319 | bytecode: hethers.utils.BytesLike, 320 | signer?: hethers.Signer 321 | ) { 322 | const {ContractFactory} = require("@hashgraph/hethers") as typeof hethers; 323 | 324 | if (signer === undefined) { 325 | const signers = await hre.hethers?.getSigners(hre); 326 | if (signers && signers.length) { 327 | // @ts-ignore 328 | signer = signers[0]; 329 | } 330 | } 331 | 332 | const abiWithAddedGas = addGasToAbiMethodsIfNecessary(hre, abi); 333 | 334 | const contractFactory = new ContractFactory(abiWithAddedGas, bytecode, signer); 335 | 336 | // @ts-ignore 337 | const defaultGasLimit = hre.config.hedera.gasLimit; 338 | 339 | // Apply the default gasLimit 340 | contractFactory.deploy = defaultNthArgument( 341 | contractFactory.deploy, 342 | contractFactory.interface.deploy.inputs.length, 343 | contractFactory, 344 | {gasLimit: defaultGasLimit}); 345 | 346 | contractFactory.getDeployTransaction = defaultNthArgument( 347 | contractFactory.getDeployTransaction, 348 | contractFactory.interface.deploy.inputs.length, 349 | contractFactory, 350 | {gasLimit: defaultGasLimit} 351 | ); 352 | 353 | return contractFactory; 354 | } 355 | 356 | export async function getContractAt( 357 | hre: HederaHardhatRuntimeEnvironment, 358 | nameOrAbi: string | any[], 359 | address: string, 360 | signer?: hethers.Signer 361 | ) { 362 | if (typeof nameOrAbi === "string") { 363 | const artifact = await hre.artifacts.readArtifact(nameOrAbi); 364 | 365 | return getContractAtFromArtifact(hre, artifact, address, signer); 366 | } 367 | 368 | const {Contract} = require("@hashgraph/hethers") as typeof hethers; 369 | 370 | if (signer === undefined) { 371 | const signers = await hre.hethers?.getSigners(hre); 372 | if (signers && signers.length) { 373 | // @ts-ignore 374 | signer = signers[0]; 375 | } 376 | } 377 | 378 | // If there's no signer, we want to put the provider for the selected network here. 379 | // This allows read only operations on the contract interface. 380 | const signerOrProvider: hethers.Signer | hethers.providers.Provider = 381 | signer !== undefined ? signer : hre.hethers?.provider; 382 | 383 | const abiWithAddedGas = addGasToAbiMethodsIfNecessary(hre, nameOrAbi); 384 | 385 | return new Contract(address, abiWithAddedGas, signerOrProvider); 386 | } 387 | 388 | export async function getContractAtFromArtifact( 389 | hre: HederaHardhatRuntimeEnvironment, 390 | artifact: Artifact, 391 | address: string, 392 | signer?: hethers.Signer 393 | ) { 394 | if (!isArtifact(artifact)) { 395 | throw new NomicLabsHardhatPluginError( 396 | pluginName, 397 | `You are trying to create a contract by artifact, but you have not passed a valid artifact parameter.` 398 | ); 399 | } 400 | 401 | const factory = await getContractFactoryByAbiAndBytecode( 402 | hre, 403 | artifact.abi, 404 | "0x", 405 | signer 406 | ); 407 | 408 | let contract = factory.attach(address); 409 | // If there's no signer, we connect the contract instance to the provider for the selected network. 410 | if (contract.provider === null) { 411 | contract = contract.connect(hre.hethers?.provider); 412 | } 413 | 414 | return contract; 415 | } 416 | 417 | 418 | // This helper adds a `gas` field to the ABI function elements if the network 419 | // is set up to use a fixed amount of gas. 420 | function addGasToAbiMethodsIfNecessary( 421 | hre: HederaHardhatRuntimeEnvironment, 422 | abi: any[] 423 | ): any[] { 424 | const networkConfig = hre.network.config 425 | const {BigNumber} = require("@hashgraph/hethers") as typeof hethers; 426 | 427 | if (networkConfig.gas === undefined) { 428 | return abi; 429 | } 430 | 431 | if (networkConfig.gas === "auto") { 432 | throw new NomicLabsHardhatPluginError( 433 | pluginName, 434 | `Automatic gas estimation is not supported.` 435 | ); 436 | } 437 | 438 | const modifiedAbi: any[] = []; 439 | 440 | for (const abiElement of abi) { 441 | if (abiElement.type !== "function") { 442 | modifiedAbi.push(abiElement); 443 | continue; 444 | } 445 | 446 | // @ts-ignore 447 | const defaultGasLimit = hre.config.hedera.gasLimit; 448 | 449 | modifiedAbi.push({ 450 | ...abiElement, 451 | gasLimit: defaultGasLimit, 452 | }); 453 | } 454 | 455 | return modifiedAbi; 456 | } 457 | 458 | 459 | function linkBytecode(artifact: Artifact, libraries: Link[]): string { 460 | let bytecode = artifact.bytecode; 461 | 462 | // TODO: measure performance impact 463 | for (const {sourceName, libraryName, address} of libraries) { 464 | const linkReferences = artifact.linkReferences[sourceName][libraryName]; 465 | for (const {start, length} of linkReferences) { 466 | bytecode = 467 | bytecode.substr(0, 2 + start * 2) + 468 | address.substr(2) + 469 | bytecode.substr(2 + (start + length) * 2); 470 | } 471 | } 472 | 473 | return bytecode; 474 | } 475 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import "mocha"; 2 | import { assert } from "chai"; 3 | import type { hethers } from "@hashgraph/hethers"; 4 | import { hethers as hethersObj } from "@hashgraph/hethers"; 5 | import { NomicLabsHardhatPluginError } from "hardhat/plugins"; 6 | import { Artifact } from "hardhat/types"; 7 | import { HethersProviderWrapper } from "../src/internal/hethers-provider-wrapper"; 8 | import { useEnvironment } from "./helpers"; 9 | import { SignerWithAddress } from "../src/internal/signers"; 10 | import path from 'path'; 11 | import dotenv from "dotenv"; 12 | dotenv.config({path: path.resolve(__dirname, '../.env')}); 13 | 14 | const test_on = process.env['RUN_TEST_ON']; 15 | // @ts-ignore 16 | const test_on_lowercase = test_on.toLowerCase(); 17 | 18 | describe("Hethers plugin", function() { 19 | useEnvironment("hardhat-project", test_on_lowercase); 20 | let wallet1: hethers.Wallet, wallet2: hethers.Wallet; 21 | 22 | before(function() { 23 | wallet1 = new hethersObj.Wallet({ 24 | // @ts-ignore 25 | "account": process.env[`${test_on}_ACCOUNT_ID_1`], 26 | // @ts-ignore 27 | "privateKey": process.env[`${test_on}_PRIVATEKEY_1`] 28 | }); 29 | 30 | wallet2 = new hethersObj.Wallet({ 31 | // @ts-ignore 32 | "account": process.env[`${test_on}_ACCOUNT_ID_2`], 33 | // @ts-ignore 34 | "privateKey": process.env[`${test_on}_PRIVATEKEY_2`] 35 | }); 36 | }); 37 | this.timeout(900000); 38 | 39 | describe("Provider", function() { 40 | it("should be able to call getBalance()", async function() { 41 | let balance = (await this.env.hethers.provider.getBalance(wallet1.account)).toString(); 42 | assert.strictEqual(balance > 0, true); 43 | }); 44 | }); 45 | 46 | describe("Signers", function() { 47 | it("should be able to get all signers", async function() { 48 | const signers = await this.env.hethers.getSigners(); 49 | 50 | assert.strictEqual(signers.length, 2); 51 | assert.strictEqual(signers[0].constructor.name, "SignerWithAddress"); 52 | }); 53 | }); 54 | describe("Signer", function() { 55 | this.timeout(60000); 56 | let signer: SignerWithAddress; 57 | it("should be able to get a signer via accountId and privateKey", async function() { 58 | signer = await this.env.hethers.getSigner({ 59 | "account": wallet1.account, 60 | "privateKey": wallet1.privateKey 61 | }); 62 | 63 | assert.strictEqual(signer.constructor.name, "SignerWithAddress"); 64 | }); 65 | it("should be able to sign a transaction", async function() { 66 | const signedTx = await signer.signTransaction({ 67 | to: wallet2.account, 68 | value: 1000 69 | }); 70 | 71 | assert.strictEqual(signedTx != null && signedTx != "0x", true); 72 | }); 73 | it("should be able to transfer tokens with the signer", async function() { 74 | const balanceBefore = (await this.env.hethers.provider.getBalance(wallet2.account)).toString(); 75 | await signer.sendTransaction({ 76 | to: wallet2.account, 77 | value: 142 78 | }); 79 | const balanceAfter = (await this.env.hethers.provider.getBalance(wallet2.account)).toString(); 80 | assert.strictEqual(this.env.hethers.BigNumber.from(balanceAfter) - this.env.hethers.BigNumber.from(balanceBefore), 142); 81 | }); 82 | }); 83 | 84 | describe("Signers and contracts helpers", function() { 85 | let signers: hethers.Signer[]; 86 | let greeterArtifact: Artifact; 87 | let iGreeterArtifact: Artifact; 88 | 89 | beforeEach(async function() { 90 | signers = await this.env.hethers.getSigners(); 91 | await this.env.run("compile", { quiet: true }); 92 | greeterArtifact = await this.env.artifacts.readArtifact("Greeter"); 93 | 94 | iGreeterArtifact = await this.env.artifacts.readArtifact("IGreeter"); 95 | }); 96 | 97 | describe("getSigners", function() { 98 | it("should return the signers", async function() { 99 | const sigs = await this.env.hethers.getSigners(); 100 | assert.equal( 101 | await sigs[0].getAddress(), 102 | wallet1.address 103 | ); 104 | }); 105 | 106 | it("should expose the identity synchronously", async function() { 107 | const sigs = await this.env.hethers.getSigners(); 108 | const identity = wallet1._signingKey(); 109 | 110 | assert.equal( 111 | sigs[0].identity.curve, 112 | identity.curve 113 | ); 114 | assert.equal( 115 | sigs[0].identity.publicKey, 116 | identity.publicKey 117 | ); 118 | assert.equal( 119 | sigs[0].identity.compressedPublicKey, 120 | identity.compressedPublicKey 121 | ); 122 | }); 123 | 124 | it("should expose the address synchronously", async function () { 125 | const sigs = await this.env.hethers.getSigners(); 126 | assert.equal( 127 | sigs[0].address, 128 | wallet1.address 129 | ); 130 | }); 131 | }); 132 | 133 | describe("signer", function() { 134 | it("should sign a message", async function() { 135 | const [sig] = await this.env.hethers.getSigners(); 136 | 137 | const result = await sig.signMessage("hello"); 138 | const hethersResult = await wallet1.signMessage("hello"); 139 | 140 | assert.equal( 141 | result, 142 | hethersResult 143 | ); 144 | }); 145 | 146 | it("should throw when sign a transaction", async function() { 147 | const [sig] = await this.env.hethers.getSigners(); 148 | 149 | const Greeter = await this.env.hethers.getContractFactory("Greeter"); 150 | const tx = Greeter.getDeployTransaction(); 151 | 152 | try { 153 | await sig.signTransaction(tx); 154 | } catch (err) { 155 | assert.exists(err); 156 | return; 157 | } 158 | assert.isTrue(false); 159 | }); 160 | 161 | it("should return the balance of the account", async function() { 162 | const [sig] = await this.env.hethers.getSigners(); 163 | assert.notEqual( 164 | (await sig.getBalance().toString), 165 | "0" 166 | ); 167 | }); 168 | 169 | it("should not allow to use the call method", async function() { 170 | const [sig] = await this.env.hethers.getSigners(); 171 | 172 | const Greeter = await this.env.hethers.getContractFactory("Greeter"); 173 | const tx = Greeter.getDeployTransaction(); 174 | try { 175 | await sig.call(tx); 176 | } catch (err) { 177 | assert.exists(err); 178 | return; 179 | } 180 | assert.isTrue(false); 181 | }); 182 | 183 | it("should send a transaction", async function() { 184 | const [sig] = await this.env.hethers.getSigners(); 185 | 186 | const Greeter = await this.env.hethers.getContractFactory("Greeter"); 187 | const tx = Greeter.getDeployTransaction(); 188 | 189 | const response = await sig.sendTransaction(tx); 190 | 191 | const receipt = await response.wait(); 192 | 193 | assert.equal(receipt.status, 1); 194 | }); 195 | 196 | xit("should get the chainId", async function() { 197 | const [sig] = await this.env.hethers.getSigners(); 198 | 199 | const chainId = await sig.getChainId(); 200 | 201 | assert.equal(chainId, 291); 202 | }); 203 | 204 | it("should check and populate a transaction", async function() { 205 | const [sig] = await this.env.hethers.getSigners(); 206 | 207 | const Greeter = await this.env.hethers.getContractFactory("Greeter"); 208 | const tx = Greeter.getDeployTransaction(); 209 | 210 | const checkedTransaction = sig.checkTransaction(tx); 211 | 212 | assert.equal(await checkedTransaction.from, sig.address); 213 | 214 | const populatedTransaction = await sig.populateTransaction( 215 | checkedTransaction 216 | ); 217 | 218 | assert.equal(populatedTransaction.from, sig.address); 219 | }); 220 | }); 221 | 222 | describe("getContractFactory", function() { 223 | describe("by name", function() { 224 | it("should return a contract factory", async function() { 225 | // It's already compiled in artifacts/ 226 | const contract = await this.env.hethers.getContractFactory( 227 | "Greeter" 228 | ); 229 | 230 | assert.containsAllKeys(contract.interface.functions, [ 231 | "setGreeting(string)", 232 | "greet()" 233 | ]); 234 | 235 | assert.equal( 236 | await contract.signer.getAddress(), 237 | await signers[0].getAddress() 238 | ); 239 | }); 240 | 241 | it("should fail to return a contract factory for an interface", async function() { 242 | try { 243 | await this.env.hethers.getContractFactory("IGreeter"); 244 | } catch (reason: any) { 245 | assert.instanceOf( 246 | reason, 247 | NomicLabsHardhatPluginError, 248 | "getContractFactory should fail with a hardhat plugin error" 249 | ); 250 | assert.isTrue( 251 | reason.message.includes("is abstract and can't be deployed"), 252 | "getContractFactory should report the abstract contract as the cause" 253 | ); 254 | return; 255 | } 256 | 257 | // The test shouldn't reach this point. 258 | assert.fail( 259 | "getContractFactory should fail with an abstract contract" 260 | ); 261 | }); 262 | 263 | it("should link a library", async function() { 264 | const libraryFactory = await this.env.hethers.getContractFactory( 265 | "TestLibrary" 266 | ); 267 | const library = await libraryFactory.deploy(); 268 | 269 | const contractFactory = await this.env.hethers.getContractFactory( 270 | "TestContractLib", 271 | { libraries: { TestLibrary: library.address } } 272 | ); 273 | assert.equal( 274 | await contractFactory.signer.getAddress(), 275 | await signers[0].getAddress() 276 | ); 277 | const numberPrinter = await contractFactory.deploy(); 278 | const someNumber = 50; 279 | assert.equal( 280 | await numberPrinter.callStatic.printNumber(someNumber), 281 | someNumber * 2 282 | ); 283 | }); 284 | 285 | it("should fail to link when passing in an ambiguous library link", async function() { 286 | const libraryFactory = await this.env.hethers.getContractFactory( 287 | "contracts/TestContractLib.sol:TestLibrary" 288 | ); 289 | const library = await libraryFactory.deploy(); 290 | 291 | try { 292 | await this.env.hethers.getContractFactory("TestContractLib", { 293 | libraries: { 294 | TestLibrary: library.address, 295 | "contracts/TestContractLib.sol:TestLibrary": library.address 296 | } 297 | }); 298 | } catch (reason: any) { 299 | assert.instanceOf( 300 | reason, 301 | NomicLabsHardhatPluginError, 302 | "getContractFactory should fail with a hardhat plugin error" 303 | ); 304 | assert.isTrue( 305 | reason.message.includes( 306 | "refer to the same library and were given as two separate library links" 307 | ), 308 | "getContractFactory should report the ambiguous link as the cause" 309 | ); 310 | assert.isTrue( 311 | reason.message.includes( 312 | "TestLibrary and contracts/TestContractLib.sol:TestLibrary" 313 | ), 314 | "getContractFactory should display the ambiguous library links" 315 | ); 316 | return; 317 | } 318 | 319 | // The test shouldn't reach this point 320 | assert.fail( 321 | "getContractFactory should fail when the link for one library is ambiguous" 322 | ); 323 | }); 324 | 325 | it("should link a library even if there's an identically named library in the project", async function() { 326 | const libraryFactory = await this.env.hethers.getContractFactory( 327 | "contracts/TestNonUniqueLib.sol:NonUniqueLibrary" 328 | ); 329 | const library = await libraryFactory.deploy(); 330 | 331 | const contractFactory = await this.env.hethers.getContractFactory( 332 | "TestNonUniqueLib", 333 | { libraries: { NonUniqueLibrary: library.address } } 334 | ); 335 | assert.equal( 336 | await contractFactory.signer.getAddress(), 337 | await signers[0].getAddress() 338 | ); 339 | }); 340 | 341 | it("should fail to link an ambiguous library", async function() { 342 | const libraryFactory = await this.env.hethers.getContractFactory( 343 | "contracts/AmbiguousLibrary.sol:AmbiguousLibrary" 344 | ); 345 | const library = await libraryFactory.deploy(); 346 | const library2Factory = await this.env.hethers.getContractFactory( 347 | "contracts/AmbiguousLibrary2.sol:AmbiguousLibrary" 348 | ); 349 | const library2 = await library2Factory.deploy(); 350 | 351 | try { 352 | await this.env.hethers.getContractFactory("TestAmbiguousLib", { 353 | libraries: { 354 | AmbiguousLibrary: library.address, 355 | "contracts/AmbiguousLibrary2.sol:AmbiguousLibrary": 356 | library2.address 357 | } 358 | }); 359 | } catch (reason: any) { 360 | assert.instanceOf( 361 | reason, 362 | NomicLabsHardhatPluginError, 363 | "getContractFactory should fail with a hardhat plugin error" 364 | ); 365 | assert.isTrue( 366 | reason.message.includes("is ambiguous for the contract"), 367 | "getContractFactory should report the ambiguous name resolution as the cause" 368 | ); 369 | assert.isTrue( 370 | reason.message.includes( 371 | "AmbiguousLibrary.sol:AmbiguousLibrary" 372 | ) && 373 | reason.message.includes( 374 | "AmbiguousLibrary2.sol:AmbiguousLibrary" 375 | ), 376 | "getContractFactory should enumerate both available library name candidates" 377 | ); 378 | return; 379 | } 380 | 381 | // The test shouldn't reach this point 382 | assert.fail( 383 | "getContractFactory should fail to retrieve an ambiguous library name" 384 | ); 385 | }); 386 | 387 | it("should fail to create a contract factory with missing libraries", async function() { 388 | try { 389 | await this.env.hethers.getContractFactory("TestContractLib"); 390 | } catch (reason: any) { 391 | assert.instanceOf( 392 | reason, 393 | NomicLabsHardhatPluginError, 394 | "getContractFactory should fail with a hardhat plugin error" 395 | ); 396 | assert.isTrue( 397 | reason.message.includes( 398 | "missing links for the following libraries" 399 | ), 400 | "getContractFactory should report the missing libraries as the cause" 401 | ); 402 | assert.isTrue( 403 | reason.message.includes("TestContractLib.sol:TestLibrary"), 404 | "getContractFactory should enumerate missing library names" 405 | ); 406 | return; 407 | } 408 | 409 | // The test shouldn't reach this point 410 | assert.fail( 411 | "getContractFactory should fail to create a contract factory if there are missing libraries" 412 | ); 413 | }); 414 | 415 | it("should fail to create a contract factory with an invalid address", async function() { 416 | const notAnAddress = "definitely not an address"; 417 | try { 418 | await this.env.hethers.getContractFactory("TestContractLib", { 419 | libraries: { TestLibrary: notAnAddress } 420 | }); 421 | } catch (reason: any) { 422 | assert.instanceOf( 423 | reason, 424 | NomicLabsHardhatPluginError, 425 | "getContractFactory should fail with a hardhat plugin error" 426 | ); 427 | assert.isTrue( 428 | reason.message.includes("invalid address"), 429 | "getContractFactory should report the invalid address as the cause" 430 | ); 431 | assert.isTrue( 432 | reason.message.includes(notAnAddress), 433 | "getContractFactory should display the invalid address" 434 | ); 435 | return; 436 | } 437 | 438 | // The test shouldn't reach this point 439 | assert.fail( 440 | "getContractFactory should fail to create a contract factory if there is an invalid address" 441 | ); 442 | }); 443 | 444 | it("should fail to create a contract factory when incorrectly linking a library with an hethers.Contract", async function() { 445 | const libraryFactory = await this.env.hethers.getContractFactory( 446 | "TestLibrary" 447 | ); 448 | const library = await libraryFactory.deploy(); 449 | 450 | try { 451 | await this.env.hethers.getContractFactory("TestContractLib", { 452 | libraries: { TestLibrary: library as any } 453 | }); 454 | } catch (reason: any) { 455 | assert.instanceOf( 456 | reason, 457 | NomicLabsHardhatPluginError, 458 | "getContractFactory should fail with a hardhat plugin error" 459 | ); 460 | assert.isTrue( 461 | reason.message.includes( 462 | "invalid address", 463 | "getContractFactory should report the invalid address as the cause" 464 | ) 465 | ); 466 | // This assert is here just to make sure we don't end up printing an enormous object 467 | // in the error message. This may happen if the argument received is particularly complex. 468 | assert.isTrue( 469 | reason.message.length <= 400, 470 | "getContractFactory should fail with an error message that isn't too large" 471 | ); 472 | return; 473 | } 474 | 475 | assert.fail( 476 | "getContractFactory should fail to create a contract factory if there is an invalid address" 477 | ); 478 | }); 479 | 480 | it("Should be able to send txs and make calls", async function() { 481 | const Greeter = await this.env.hethers.getContractFactory("Greeter"); 482 | const greeter = await Greeter.deploy(); 483 | 484 | assert.equal(await greeter.functions.greet(), "Hi"); 485 | await greeter.functions.setGreeting("Hola"); 486 | assert.equal(await greeter.functions.greet(), "Hola"); 487 | }); 488 | 489 | it("Should be able to deploy contracts with arguments in constructor", async function() { 490 | const GreeterWithArgs = await this.env.hethers.getContractFactory("GreeterWithArgs"); 491 | const greeter = await GreeterWithArgs.deploy("SomeArgument"); 492 | assert.equal(await greeter.functions.greet(), "SomeArgument"); 493 | }); 494 | 495 | it("Should throw the correct error messages when deploying with incorrect number of arguments", async function() { 496 | const GreeterWithArgs = await this.env.hethers.getContractFactory("GreeterWithArgs"); 497 | 498 | try { 499 | const greeter = await GreeterWithArgs.deploy("SomeArgument", "ExtraArgument"); 500 | } catch (err: any) { 501 | assert.exists(err); 502 | assert.equal(err.code, 'UNEXPECTED_ARGUMENT'); 503 | assert.equal(err.reason, 'too many arguments: in Contract constructor'); 504 | assert.equal(err.count, 2); 505 | assert.equal(err.expectedCount, 1); 506 | } 507 | 508 | try { 509 | const greeter = await GreeterWithArgs.deploy("SomeArgument", "ExtraArgument", "ExtraExtraArgument"); 510 | } catch (err: any) { 511 | assert.exists(err); 512 | assert.equal(err.code, 'UNEXPECTED_ARGUMENT'); 513 | assert.equal(err.reason, 'too many arguments: in Contract constructor'); 514 | assert.equal(err.count, 3); 515 | assert.equal(err.expectedCount, 1); 516 | } 517 | 518 | try { 519 | const greeter = await GreeterWithArgs.deploy(); 520 | } catch (err: any) { 521 | assert.exists(err); 522 | assert.equal(err.code, 'MISSING_ARGUMENT'); 523 | assert.equal(err.reason, 'missing argument: in Contract constructor'); 524 | assert.equal(err.count, 0); 525 | assert.equal(err.expectedCount, 1); 526 | return; 527 | } 528 | 529 | assert.isTrue(false); 530 | }); 531 | 532 | it("Should be able to deploy contracts with arguments in constructor and manually set gasLimit", async function() { 533 | const GreeterWithArgs = await this.env.hethers.getContractFactory("GreeterWithArgs"); 534 | const greeter = await GreeterWithArgs.deploy("SomeArgument", {gasLimit: 300000}); 535 | assert.equal(await greeter.functions.greet(), "SomeArgument"); 536 | }); 537 | 538 | describe("with custom signer", function() { 539 | it("should return a contract factory connected to the custom signer", async function() { 540 | // It's already compiled in artifacts/ 541 | const contract = await this.env.hethers.getContractFactory( 542 | "Greeter", 543 | signers[1] 544 | ); 545 | 546 | assert.containsAllKeys(contract.interface.functions, [ 547 | "setGreeting(string)", 548 | "greet()" 549 | ]); 550 | 551 | assert.equal( 552 | await contract.signer.getAddress(), 553 | await signers[1].getAddress() 554 | ); 555 | }); 556 | }); 557 | }); 558 | 559 | describe("by abi and bytecode", function() { 560 | it("should return a contract factory", async function() { 561 | // It's already compiled in artifacts/ 562 | const contract = await this.env.hethers.getContractFactory( 563 | greeterArtifact.abi, 564 | greeterArtifact.bytecode 565 | ); 566 | 567 | assert.containsAllKeys(contract.interface.functions, [ 568 | "setGreeting(string)", 569 | "greet()" 570 | ]); 571 | 572 | assert.equal( 573 | await contract.signer.getAddress(), 574 | await signers[0].getAddress() 575 | ); 576 | }); 577 | 578 | it("should return a contract factory for an interface", async function() { 579 | const contract = await this.env.hethers.getContractFactory( 580 | iGreeterArtifact.abi, 581 | iGreeterArtifact.bytecode 582 | ); 583 | assert.equal(contract.bytecode, "0x"); 584 | assert.containsAllKeys(contract.interface.functions, ["greet()"]); 585 | 586 | assert.equal( 587 | await contract.signer.getAddress(), 588 | await signers[0].getAddress() 589 | ); 590 | }); 591 | 592 | it("Should be able to send txs and make calls", async function() { 593 | const Greeter = await this.env.hethers.getContractFactory( 594 | greeterArtifact.abi, 595 | greeterArtifact.bytecode 596 | ); 597 | const greeter = await Greeter.deploy(); 598 | 599 | assert.equal(await greeter.functions.greet(), "Hi"); 600 | await greeter.functions.setGreeting("Hola"); 601 | assert.equal(await greeter.functions.greet(), "Hola"); 602 | }); 603 | 604 | describe("with custom signer", function() { 605 | it("should return a contract factory connected to the custom signer", async function() { 606 | // It's already compiled in artifacts/ 607 | const contract = await this.env.hethers.getContractFactory( 608 | greeterArtifact.abi, 609 | greeterArtifact.bytecode, 610 | signers[1] 611 | ); 612 | 613 | assert.containsAllKeys(contract.interface.functions, [ 614 | "setGreeting(string)", 615 | "greet()" 616 | ]); 617 | 618 | assert.equal( 619 | await contract.signer.getAddress(), 620 | await signers[1].getAddress() 621 | ); 622 | }); 623 | }); 624 | }); 625 | }); 626 | 627 | describe("getContractFactoryFromArtifact", function() { 628 | 629 | it("should return a contract factory", async function() { 630 | const contract = await this.env.hethers.getContractFactoryFromArtifact( 631 | greeterArtifact 632 | ); 633 | 634 | assert.containsAllKeys(contract.interface.functions, [ 635 | "setGreeting(string)", 636 | "greet()" 637 | ]); 638 | 639 | assert.equal( 640 | await contract.signer.getAddress(), 641 | await signers[0].getAddress() 642 | ); 643 | }); 644 | 645 | it("should link a library", async function() { 646 | const libraryFactory = await this.env.hethers.getContractFactory( 647 | "TestLibrary" 648 | ); 649 | const library = await libraryFactory.deploy(); 650 | 651 | const testContractLibArtifact = await this.env.artifacts.readArtifact( 652 | "TestContractLib" 653 | ); 654 | 655 | const contractFactory = 656 | await this.env.hethers.getContractFactoryFromArtifact( 657 | testContractLibArtifact, 658 | { libraries: { TestLibrary: library.address } } 659 | ); 660 | 661 | assert.equal( 662 | await contractFactory.signer.getAddress(), 663 | await signers[0].getAddress() 664 | ); 665 | const numberPrinter = await contractFactory.deploy(); 666 | const someNumber = 50; 667 | assert.equal( 668 | await numberPrinter.callStatic.printNumber(someNumber), 669 | someNumber * 2 670 | ); 671 | }); 672 | 673 | it("Should be able to send txs and make calls", async function() { 674 | const Greeter = await this.env.hethers.getContractFactoryFromArtifact( 675 | greeterArtifact 676 | ); 677 | const greeter = await Greeter.deploy(); 678 | 679 | assert.equal(await greeter.functions.greet(), "Hi"); 680 | await greeter.functions.setGreeting("Hola"); 681 | assert.equal(await greeter.functions.greet(), "Hola"); 682 | }); 683 | 684 | describe("with custom signer", function() { 685 | it("should return a contract factory connected to the custom signer", async function() { 686 | const contract = 687 | await this.env.hethers.getContractFactoryFromArtifact( 688 | greeterArtifact, 689 | signers[1] 690 | ); 691 | 692 | assert.containsAllKeys(contract.interface.functions, [ 693 | "setGreeting(string)", 694 | "greet()" 695 | ]); 696 | 697 | assert.equal( 698 | await contract.signer.getAddress(), 699 | await signers[1].getAddress() 700 | ); 701 | }); 702 | }); 703 | }); 704 | 705 | describe("getContractAt", function() { 706 | let deployedGreeter: hethers.Contract; 707 | 708 | beforeEach(async function() { 709 | const Greeter = await this.env.hethers.getContractFactory("Greeter"); 710 | deployedGreeter = await Greeter.deploy(); 711 | }); 712 | 713 | describe("by name and address", function() { 714 | it("Should return an instance of a contract", async function() { 715 | const contract = await this.env.hethers.getContractAt( 716 | "Greeter", 717 | deployedGreeter.address 718 | ); 719 | 720 | assert.containsAllKeys(contract.functions, [ 721 | "setGreeting(string)", 722 | "greet()" 723 | ]); 724 | 725 | assert.equal( 726 | await contract.signer.getAddress(), 727 | await signers[0].getAddress() 728 | ); 729 | }); 730 | 731 | it("Should return an instance of an interface", async function() { 732 | const contract = await this.env.hethers.getContractAt( 733 | "IGreeter", 734 | deployedGreeter.address 735 | ); 736 | 737 | assert.containsAllKeys(contract.functions, ["greet()"]); 738 | 739 | assert.equal( 740 | await contract.signer.getAddress(), 741 | await signers[0].getAddress() 742 | ); 743 | }); 744 | 745 | it("Should be able to send txs and make calls", async function() { 746 | const greeter = await this.env.hethers.getContractAt( 747 | "Greeter", 748 | deployedGreeter.address 749 | ); 750 | 751 | assert.equal(await greeter.functions.greet(), "Hi"); 752 | await greeter.functions.setGreeting("Hola"); 753 | assert.equal(await greeter.functions.greet(), "Hola"); 754 | }); 755 | 756 | describe("with custom signer", function() { 757 | it("Should return an instance of a contract associated to a custom signer", async function() { 758 | const contract = await this.env.hethers.getContractAt( 759 | "Greeter", 760 | deployedGreeter.address, 761 | signers[1] 762 | ); 763 | 764 | assert.equal( 765 | await contract.signer.getAddress(), 766 | await signers[1].getAddress() 767 | ); 768 | }); 769 | }); 770 | }); 771 | 772 | describe("by abi and address", function() { 773 | it("Should return an instance of a contract", async function() { 774 | const contract = await this.env.hethers.getContractAt( 775 | greeterArtifact.abi, 776 | deployedGreeter.address 777 | ); 778 | 779 | assert.containsAllKeys(contract.functions, [ 780 | "setGreeting(string)", 781 | "greet()" 782 | ]); 783 | 784 | assert.equal( 785 | await contract.signer.getAddress(), 786 | await signers[0].getAddress() 787 | ); 788 | }); 789 | 790 | it("Should return an instance of an interface", async function() { 791 | const contract = await this.env.hethers.getContractAt( 792 | iGreeterArtifact.abi, 793 | deployedGreeter.address 794 | ); 795 | 796 | assert.containsAllKeys(contract.functions, ["greet()"]); 797 | 798 | assert.equal( 799 | await contract.signer.getAddress(), 800 | await signers[0].getAddress() 801 | ); 802 | }); 803 | 804 | it("Should be able to send txs and make calls", async function() { 805 | const greeter = await this.env.hethers.getContractAt( 806 | greeterArtifact.abi, 807 | deployedGreeter.address 808 | ); 809 | 810 | assert.equal(await greeter.functions.greet(), "Hi"); 811 | await greeter.functions.setGreeting("Hola"); 812 | assert.equal(await greeter.functions.greet(), "Hola"); 813 | }); 814 | 815 | it("Should be able to detect events", async function() { 816 | 817 | const greeter = await this.env.hethers.getContractAt( 818 | greeterArtifact.abi, 819 | deployedGreeter.address 820 | ); 821 | 822 | const provider = greeter.provider as HethersProviderWrapper; 823 | provider.pollingInterval = 1000; 824 | 825 | let eventEmitted = false; 826 | greeter.on("GreetingUpdated", () => { 827 | eventEmitted = true; 828 | }); 829 | 830 | await greeter.setGreeting("Hola"); 831 | 832 | // wait for 1.5 polling intervals for the event to fire 833 | await new Promise((resolve) => 834 | setTimeout(resolve, provider.pollingInterval * 20) 835 | ); 836 | 837 | assert.equal(eventEmitted, true); 838 | }); 839 | 840 | describe("with custom signer", function() { 841 | it("Should return an instance of a contract associated to a custom signer", async function() { 842 | const contract = await this.env.hethers.getContractAt( 843 | greeterArtifact.abi, 844 | deployedGreeter.address, 845 | signers[1] 846 | ); 847 | 848 | assert.equal( 849 | await contract.signer.getAddress(), 850 | await signers[1].getAddress() 851 | ); 852 | }); 853 | }); 854 | 855 | it("should work with linked contracts", async function() { 856 | const libraryFactory = await this.env.hethers.getContractFactory( 857 | "TestLibrary" 858 | ); 859 | const library = await libraryFactory.deploy(); 860 | 861 | const contractFactory = await this.env.hethers.getContractFactory( 862 | "TestContractLib", 863 | { libraries: { TestLibrary: library.address } } 864 | ); 865 | const numberPrinter = await contractFactory.deploy(); 866 | 867 | const numberPrinterAtAddress = await this.env.hethers.getContractAt( 868 | "TestContractLib", 869 | numberPrinter.address 870 | ); 871 | 872 | const someNumber = 50; 873 | assert.equal( 874 | await numberPrinterAtAddress.callStatic.printNumber(someNumber), 875 | someNumber * 2 876 | ); 877 | }); 878 | }); 879 | }); 880 | 881 | describe("getContractAtFromArtifact", function() { 882 | let deployedGreeter: hethers.Contract; 883 | 884 | beforeEach(async function() { 885 | const Greeter = await this.env.hethers.getContractFactory("Greeter"); 886 | deployedGreeter = await Greeter.deploy(); 887 | }); 888 | 889 | describe("by artifact and address", function() { 890 | it("Should return an instance of a contract", async function() { 891 | const contract = await this.env.hethers.getContractAtFromArtifact( 892 | greeterArtifact, 893 | deployedGreeter.address 894 | ); 895 | 896 | assert.containsAllKeys(contract.functions, [ 897 | "setGreeting(string)", 898 | "greet()" 899 | ]); 900 | 901 | assert.equal( 902 | await contract.signer.getAddress(), 903 | await signers[0].getAddress() 904 | ); 905 | }); 906 | 907 | it("Should be able to send txs and make calls", async function() { 908 | const signers = await this.env.hethers.getSigners(); 909 | const greeter = await this.env.hethers.getContractAtFromArtifact( 910 | greeterArtifact, 911 | deployedGreeter.address 912 | ); 913 | 914 | assert.equal(await greeter.functions.greet(), "Hi"); 915 | const receipt = await greeter.functions.setGreeting("Hola"); 916 | assert.equal(await greeter.functions.greet(), "Hola"); 917 | assert.equal(receipt.from, signers[0].address); 918 | }); 919 | 920 | it("Should be able to connect different signer and send txs and make calls", async function() { 921 | const signers = await this.env.hethers.getSigners(); 922 | 923 | const greeter = await this.env.hethers.getContractAtFromArtifact( 924 | greeterArtifact, 925 | deployedGreeter.address 926 | ); 927 | 928 | const receipt = await greeter.connect(signers[1]).functions.setGreeting("Hola from the second signer"); 929 | 930 | assert.equal(await greeter.functions.greet(), "Hola from the second signer"); 931 | assert.equal(receipt.from, signers[1].address); 932 | assert.notEqual(receipt.from, signers[0].address); 933 | }); 934 | 935 | describe("with custom signer", function() { 936 | it("Should return an instance of a contract associated to a custom signer", async function() { 937 | const contract = await this.env.hethers.getContractAtFromArtifact( 938 | greeterArtifact, 939 | deployedGreeter.address, 940 | signers[1] 941 | ); 942 | 943 | assert.equal( 944 | await contract.signer.getAddress(), 945 | await signers[1].getAddress() 946 | ); 947 | }); 948 | }); 949 | }); 950 | }); 951 | }); 952 | 953 | }); 954 | 955 | 956 | 957 | 958 | --------------------------------------------------------------------------------