Added more tests

This commit is contained in:
Richard Moore 2022-11-04 18:11:38 -04:00
parent 8049071152
commit b30597c686
8 changed files with 497 additions and 54 deletions

@ -6,7 +6,7 @@ import {
InfuraProvider,
// PocketProvider,
// FallbackProvider,
FallbackProvider,
isError,
} from "../index.js";
@ -79,23 +79,19 @@ const ProviderCreators: Array<ProviderCreator> = [
}
},
*/
/*
{
name: "FallbackProvider",
networks: ethNetworks,
create: function(network: string) {
const providers: Array<AbstractProvider> = [];
for (const creator of ProviderCreators) {
if (creator.name === "FallbackProvider") { continue; }
if (creator.networks.indexOf(network) >= 0) {
const provider = creator.create(network);
if (provider) { providers.push(provider); }
}
for (const providerName of [ "AlchemyProvider", "AnkrProvider", "EtherscanProvider", "InfuraProvider" ]) {
const provider = getProvider(providerName, network);
if (provider) { providers.push(provider); }
}
if (providers.length === 0) { throw new Error("UNSUPPORTED NETWORK"); }
return new FallbackProvider(providers);
}
},
*/
];
export const providerNames = Object.freeze(ProviderCreators.map((c) => (c.name)));

@ -1,40 +1,181 @@
/*
import assert from "assert";
import { connect } from "./create-provider.js";
import { getProvider } from "./create-provider.js";
import { Contract } from "../index.js";
import { Contract, EventLog, Wallet } from "../index.js";
import type { ContractEventPayload, ContractEventName, Log } from "../index.js";
describe("Test Contract", function() {
it("tests contract @TODO: expand", async function() {
const provider = connect("mainnet");
const addr = "0x99417252Aad7B065940eBdF50d665Fb8879c5958";
const abi = [
"error CustomError1(uint256 code, string message)",
const contract = new Contract("dai.tokens.ethers.eth", [
"function balanceOf(address) view returns (uint)"
], provider);
"event EventUint256(uint256 indexed value)",
"event EventAddress(address indexed value)",
"event EventString(string value)",
"event EventBytes(bytes value)",
assert.equal(await contract.balanceOf("ricmoo.firefly.eth"), BigInt("6015089439794538201631"));
"function testCustomError1(bool pass, uint code, string calldata message) pure returns (uint256)",
"function testErrorString(bool pass, string calldata message) pure returns (uint256)",
"function testPanic(uint256 code) returns (uint256)",
"function testEvent(uint256 valueUint256, address valueAddress, string valueString, bytes valueBytes) public",
"function testCallAdd(uint256 a, uint256 b) pure returns (uint256 result)"
];
it("tests contract calls", async function() {
this.timeout(10000);
const provider = getProvider("InfuraProvider", "goerli");
const contract = new Contract(addr, abi, provider);
assert.equal(await contract.testCallAdd(4, 5), BigInt(9), "testCallAdd(4, 5)");
assert.equal(await contract.testCallAdd(6, 0), BigInt(6), "testCallAdd(6, 0)");
});
it("tests events", async function() {
this.timeout(60000);
const provider = getProvider("InfuraProvider", "goerli");
assert.ok(provider);
const contract = new Contract(addr, abi, provider);
const signer = new Wallet(<string>(process.env.FAUCET_PRIVATEKEY), provider);
const contractSigner = <any>contract.connect(signer);
const vUint256 = 42;
const vAddrName = "ethers.eth";
const vAddr = "0x228568EA92aC5Bc281c1E30b1893735c60a139F1";
const vString = "Hello";
const vBytes = "0x12345678";
let hash: null | string = null;
// Test running a listener for a specific event
const specificEvent = new Promise((resolve, reject) => {
contract.on("EventUint256", async (value, event) => {
// Triggered by someone else
if (hash == null || hash !== event.log.transactionHash) { return; }
try {
assert.equal(event.filter, "EventUint256", "event.filter");
assert.equal(event.fragment.name, "EventUint256", "event.fragment.name");
assert.equal(event.log.address, addr, "event.log.address");
assert.equal(event.args.length, 1, "event.args.length");
assert.equal(event.args[0], BigInt(42), "event.args[0]");
const count = await contract.listenerCount("EventUint256");
await event.removeListener();
assert.equal(await contract.listenerCount("EventUint256"), count - 1, "decrement event count");
resolve(null);
} catch (e) {
event.removeListener();
reject(e);
}
});
});
// Test running a listener on all (i.e. "*") events
const allEvents = new Promise((resolve, reject) => {
const waitingFor: Record<string, any> = {
EventUint256: vUint256,
EventAddress: vAddr,
EventString: vString,
EventBytes: vBytes
};
contract.on("*", (event: ContractEventPayload) => {
// Triggered by someone else
if (hash == null || hash !== event.log.transactionHash) { return; }
try {
const name = event.eventName;
assert.equal(event.args[0], waitingFor[name], `${ name }`);
delete waitingFor[name];
if (Object.keys(waitingFor).length === 0) {
event.removeListener();
resolve(null);
}
} catch (error) {
reject(error);
}
});
});
// Send a transaction to trigger some events
const tx = await contractSigner.testEvent(vUint256, vAddr, vString, vBytes);
hash = tx.hash;
const checkEvent = (filter: ContractEventName, event: EventLog | Log) => {
const values: Record<string, any> = {
EventUint256: vUint256,
EventString: vString,
EventAddress: vAddr,
EventBytes: vBytes
};
assert.ok(event instanceof EventLog, `queryFilter(${ filter }):isEventLog`);
const name = event.eventName;
assert.equal(event.address, addr, `queryFilter(${ filter }):address`);
assert.equal(event.args[0], values[name], `queryFilter(${ filter }):args[0]`);
};
const checkEventFilter = async (filter: ContractEventName) => {
const events = (await contract.queryFilter(filter, -10)).filter((e) => (e.transactionHash === hash));
assert.equal(events.length, 1, `queryFilter(${ filter }).length`);
checkEvent(filter, events[0]);
return events[0];
};
const receipt = await tx.wait();
// Check the logs in the receipt
for (const log of receipt.logs) { checkEvent("receipt", log); }
// Various options for queryFilter
await checkEventFilter("EventUint256");
await checkEventFilter([ "EventUint256" ]);
await checkEventFilter([ [ "EventUint256" ] ]);
await checkEventFilter("EventUint256(uint)");
await checkEventFilter([ "EventUint256(uint)" ]);
await checkEventFilter([ [ "EventUint256(uint)" ] ]);
await checkEventFilter([ [ "EventUint256", "EventUint256(uint)" ] ]);
await checkEventFilter("0x85c55bbb820e6d71c71f4894e57751de334b38c421f9c170b0e66d32eafea337");
// Query by Event
await checkEventFilter(contract.filters.EventUint256);
// Query by Deferred Topic Filter; address
await checkEventFilter(contract.filters.EventUint256(vUint256));
// Query by Deferred Topic Filter; address
await checkEventFilter(contract.filters.EventAddress(vAddr));
// Query by Deferred Topic Filter; ENS name => address
await checkEventFilter(contract.filters.EventAddress(vAddrName));
// Multiple Methods
{
const filter = [ [ "EventUint256", "EventString" ] ];
const events = (await contract.queryFilter(filter, -10)).filter((e) => (e.transactionHash === hash));
assert.equal(events.length, 2, `queryFilter(${ filter }).length`);
for (const event of events) { checkEvent(filter, event); }
}
await specificEvent;
await allEvents;
});
});
*/
/*
import { Typed } from "../abi/index.js";
import * as providers from "../providers/index.js";
import { Contract } from "../index.js";
import { log } from "./utils.js";
*/
//import type { Addressable } from "@ethersproject/address";
//import type { BigNumberish } from "@ethersproject/logger";
/*
import type {
ConstantContractMethod, ContractMethod, ContractEvent
} from "../index.js";
*/
// @TODO
/*
describe("Test Contract Calls", function() {
it("finds typed methods", async function() {
@ -48,8 +189,7 @@ describe("Test Contract Calls", function() {
contract["foo(string)"].fragment
});
});
*/
/*
describe("Test Contract Interface", function() {
it("builds contract interfaces", async function() {
this.timeout(60000);
@ -105,14 +245,3 @@ describe("Test Contract Interface", function() {
});
});
*/
/*
describe("Test Contract Calls", function() {
it("calls ERC-20 methods", async function() {
const provider = new providers.AnkrProvider();
const contract = new Contract("0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72", [
"function balanceOf(address owner) view returns (uint)",
], provider);
log(this, `balance: ${ await contract.balanceOf("0x5555763613a12D8F3e73be831DFf8598089d3dCa") }`);
});
});
*/

@ -2,7 +2,8 @@ import assert from "assert";
import {
hashMessage,
solidityPackedKeccak256, solidityPackedSha256
solidityPacked, solidityPackedKeccak256, solidityPackedSha256,
isError
} from "../index.js";
import { loadTests } from "./utils.js"
@ -157,4 +158,21 @@ describe("Test Solidity Hash functions", function() {
assert.equal(solidityPackedSha256(test.types, test.values), test.sha256);
});
}
})
const badTypes = [
{ types: [ "uint5" ], values: [ 1 ] },
{ types: [ "bytes0" ], values: [ "0x" ] },
{ types: [ "blorb" ], values: [ false ] },
];
for (const { types, values } of badTypes) {
it("fails on invalid type", function() {
assert.throws(function() {
const result = solidityPacked(types, values);
console.log(result);
}, function (error) {
return (isError(error, "INVALID_ARGUMENT") && error.argument === "type");
});
});
}
});

@ -0,0 +1,48 @@
import assert from "assert";
import { Wallet } from "../index.js";
import { getProvider, providerNames } from "./create-provider.js";
describe("Sends Transactions", function() {
const cleanup: Array<() => void> = [ ];
after(function() {
for (const func of cleanup) { func(); }
});
const wallet = new Wallet(<string>(process.env.FAUCET_PRIVATEKEY));
const networkName = "goerli";
for (const providerName of providerNames) {
const provider = getProvider(providerName, networkName);
if (provider == null) { continue; }
// Shutdown socket-based provider, otherwise its socket will prevent
// this process from exiting
if ((<any>provider).destroy) { cleanup.push(() => { (<any>provider).destroy(); }); }
it(`tests sending: ${ providerName }.`, async function() {
this.timeout(60000);
const w = wallet.connect(provider);
const dustAddr = Wallet.createRandom().address;
const tx = await w.sendTransaction({
to: dustAddr,
value: 42,
type: 2
});
const receipt = await provider.waitForTransaction(tx.hash); //tx.wait();
console.log(receipt);
const balance = await provider.getBalance(dustAddr);
assert.equal(balance, BigInt(42), "target balance after send");
});
}
});

@ -0,0 +1,73 @@
import assert from "assert";
import { loadTests } from "./utils.js";
import { formatEther, formatUnits, parseEther, parseUnits } from "../index.js";
import type { TestCaseUnit } from "./types.js";
describe("Tests unit conversion", function() {
const tests = loadTests<TestCaseUnit>("units");
const units = [
{ unit: "ether", format: "ether_format", decimals: 18 },
{ unit: "kwei", format: "kwei_format", decimals: 3 },
{ unit: "mwei", format: "mwei_format", decimals: 6 },
{ unit: "gwei", format: "gwei_format", decimals: 9 },
{ unit: "szabo", format: "szabo_format", decimals: 12 },
{ unit: "finney", format: "finney_format", decimals: 15 },
];
for (const { unit, format, decimals } of units) {
for (const test of tests) {
const str = <string | null>((<any>test)[format]);
if (str == null) { continue; }
it(`converts wei to ${ unit } string: ${ test.name }`, function() {
const wei = BigInt(test.wei);
if (decimals === 18) {
assert.equal(formatEther(wei), str, "formatEther");
assert.equal(formatUnits(wei), str, "formatUnits");
}
assert.equal(formatUnits(wei, unit), str, `formatUnits(${ unit })`);
assert.equal(formatUnits(wei, decimals), str, `formatUnits(${ decimals })`);
});
}
for (const test of tests) {
const str = <string | null>((<any>test)[format]);
if (str == null) { continue; }
it(`converts ${ format } string to wei: ${ test.name }`, function() {
const wei = BigInt(test.wei);
if (decimals === 18) {
assert.equal(parseEther(str), wei, "parseEther");
assert.equal(parseUnits(str), wei, "parseUnits");
}
assert.equal(parseUnits(str, unit), wei, `parseUnits(${ unit })`);
assert.equal(parseUnits(str, decimals), wei, `parseUnits(${ decimals })`);
});
}
}
});
describe("Tests bad unit conversion", function() {
it("fails to convert non-string value", function() {
assert.throws(() => {
parseUnits(<any>3, "ether");
}, (error: any) => {
return error.message.startsWith("value must be a string");
});
});
it("fails to convert unknown unit", function() {
assert.throws(() => {
parseUnits("3", "foobar");
}, (error: any) => {
return error.message.startsWith("invalid unit");
});
});
});

@ -0,0 +1,152 @@
import assert from "assert";
import {
toUtf8Bytes, toUtf8CodePoints, toUtf8String,
Utf8ErrorFuncs
} from "../index.js";
export type TestCaseBadString = {
name: string,
bytes: Uint8Array,
ignore: string,
replace: string,
error: string
};
export type TestCaseCodePoints = {
name: string;
text: string;
codepoints: Array<number>;
};
describe("Tests UTF-8 bad strings", function() {
const tests: Array<TestCaseBadString> = [
{
name: "unexpected continue",
bytes: new Uint8Array([ 0x41, 0x80, 0x42, 0x43 ]),
ignore: "ABC",
replace: "A\ufffdBC",
error: "UNEXPECTED_CONTINUE"
},
{
name: "bad prefix",
bytes: new Uint8Array([ 0x41, 0xf8, 0x42, 0x43 ]),
ignore: "ABC",
replace: "A\ufffdBC",
error: "BAD_PREFIX"
},
{
name: "bad prefix (multiple)",
bytes: new Uint8Array([ 0x41, 0xf8, 0x88, 0x88, 0x42, 0x43 ]),
ignore: "ABC",
replace: "A\ufffdBC",
error: "BAD_PREFIX"
},
{
name: "OVERRUN",
bytes: new Uint8Array([ 0x41, 0x42, 0xe2, 0x82 /* 0xac */ ]),
ignore: "AB",
replace: "AB\ufffd",
error: "OVERRUN"
},
{
name: "missing continue",
bytes: new Uint8Array([ 0x41, 0x42, 0xe2, 0xe2, 0x82, 0xac, 0x43 ]),
ignore: "AB\u20acC",
replace: "AB\ufffd\u20acC",
error: "MISSING_CONTINUE"
},
{
name: "out-of-range",
bytes: new Uint8Array([ 0x41, 0x42, 0xf7, 0xbf, 0xbf, 0xbf, 0x43 ]),
ignore: "ABC",
replace: "AB\ufffdC",
error: "OUT_OF_RANGE"
},
{
name: "UTF-16 surrogate (low)",
bytes: new Uint8Array([ 0x41, 0x42, 0xed, 0xa0, 0x80, 0x43 ]),
ignore: "ABC",
replace: "AB\ufffdC",
error: "UTF16_SURROGATE"
},
{
name: "UTF-16 surrogate (high)",
bytes: new Uint8Array([ 0x41, 0x42, 0xed, 0xbf, 0xbf, 0x43 ]),
ignore: "ABC",
replace: "AB\ufffdC",
error: "UTF16_SURROGATE"
},
{
name: "overlong",
bytes: new Uint8Array([ 0xf0, 0x82, 0x82, 0xac ]),
ignore: "",
replace: "\u20ac",
error: "OVERLONG"
}
];
for (const { name, bytes, ignore, replace, error } of tests) {
it(`correctly handles ${ name }: replace strategy`, function() {
const result = toUtf8String(bytes, Utf8ErrorFuncs.replace);
assert.equal(result, replace);
});
it(`correctly handles ${ name }: ignore strategy`, function() {
const result = toUtf8String(bytes, Utf8ErrorFuncs.ignore);
assert.equal(result, ignore);
});
it(`correctly handles ${ name }: error strategy`, function() {
assert.throws(() => {
const result = toUtf8String(bytes);
console.log(result);
}, (e: any) => {
return (e.message.indexOf(error) >= 0);
});
});
}
it("fails to get UTF-8 bytes from incomplete surrogate", function() {
assert.throws(() => {
const text = String.fromCharCode(0xd800);;
const result = toUtf8Bytes(text);
console.log(result);
}, (error: any) => {
return (error.message.startsWith("invalid surrogate pair"));
});
});
it("fails to get UTF-8 bytes from invalid surrogate pair", function() {
assert.throws(() => {
const text = String.fromCharCode(0xd800, 0xdbff);;
const result = toUtf8Bytes(text);
console.log(result);
}, (error: any) => {
return (error.message.startsWith("invalid surrogate pair"));
});
});
});
describe("Tests UTF-8 bad strings", function() {
const tests: Array<TestCaseCodePoints> = [
{
name: "the Euro symbol",
text: "AB\u20acC",
codepoints: [ 0x41, 0x42, 0x20ac, 0x43 ]
},
];
for (const { name, text, codepoints } of tests) {
it(`expands strings to codepoints: ${ name }`, function() {
const result = toUtf8CodePoints(text);
assert.equal(result.length, codepoints.length, "codepoints.length");
for (let i = 0; i < result.length; i++) {
assert.equal(result[i], codepoints[i], `codepoints[${ i }]`);
}
});
}
});

@ -150,6 +150,24 @@ export interface TestCaseSolidityHash {
/////////////////////////////
// rlp
export interface TestCaseUnit {
name: string;
wei: string;
ethers: string;
ether_format: string;
kwei?: string;
mwei?: string;
gwei?: string;
szabo?: string;
finney?: string;
finney_format?: string;
szabo_format?: string;
gwei_format?: string;
mwei_format?: string;
kwei_format?: string;
}
export type NestedHexString = string | Array<string | NestedHexString>;
export interface TestCaseRlp {

@ -42,6 +42,8 @@ export interface MochaRunnable {
const ATTEMPTS = 5;
export async function retryIt(name: string, func: (this: MochaRunnable) => Promise<void>): Promise<void> {
//const errors: Array<Error> = [ ];
it(name, async function() {
this.timeout(ATTEMPTS * 5000);
@ -53,15 +55,20 @@ export async function retryIt(name: string, func: (this: MochaRunnable) => Promi
if (error.message === "sync skip; aborting execution") {
// Skipping a test; let mocha handle it
throw error;
} else if (error.code === "ERR_ASSERTION") {
// Assertion error; let mocha scold us
throw error;
} else {
//errors.push(error);
if (i === ATTEMPTS - 1) {
stats.pushRetry(i, name, error);
throw error;
//stats.pushRetry(i, name, error);
} else {
await stall(500 * (1 << i));
stats.pushRetry(i, name, null);
//stats.pushRetry(i, name, null);
}
}
}
@ -72,6 +79,7 @@ export async function retryIt(name: string, func: (this: MochaRunnable) => Promi
});
}
/*
export interface StatSet {
name: string;
retries: Array<{ message: string, error: null | Error }>;
@ -80,11 +88,11 @@ export interface StatSet {
const _guard = { };
export class Stats {
#stats: Array<StatSet>;
// #stats: Array<StatSet>;
constructor(guard: any) {
if (guard !== _guard) { throw new Error("private constructor"); }
this.#stats = [ ];
// this.#stats = [ ];
}
#currentStats(): StatSet {
@ -124,3 +132,4 @@ export class Stats {
}
export const stats = new Stats(_guard);
*/