Repository: shazow/whatsabi (ts)

Issue:
providers: Incompatibility Between `WithCachedCode()` and `ethers.JsonRpcProvider`
The provider parameter passed to `providers.WithCachedCode()` appears to be incompatible with `ethers.JsonRpcProvider`.

### Type error
```sh
Provider is not assignable to parameter of type
Type 'Provider' is missing the following properties from type 'Provider': getStorageAt, getAddress
```

### Reproducible example
```ts
import { ethers } from "ethers";

function invokeAutoload(bytecode: string) {
    const provider = new ethers.JsonRpcProvider(config.rpc);
    const cachedCodeProvider = whatsabi.providers.WithCachedCode(
      provider, // Type error
      {
        [address]: bytecode,
      }
    );
    // ...
}
```

### Proposed Fix
```diff
- export function WithCachedCode(provider: Provider, codeCache: Record<string, string>): Provider {
+ export function WithCachedCode(provider: Provider, codeCache: Record<string, string>): AnyProvider {
    return {
        ...provider,
        async getCode(address: string): Promise<string> {
            if (codeCache[address]) {
                return codeCache[address];
            }
            return await provider.getCode(address);
        }
    };
}
```

If this solution seems appropriate, I’d be happy to go ahead and create a pull request.

Pull request description:
providers: WithCachedCode should accept AnyProvider and make it compatible
Fixes #133

Patch:
diff --git a/src/providers.ts b/src/providers.ts
index 6c1f689..a3a2535 100644
--- a/src/providers.ts
+++ b/src/providers.ts
@@ -18,7 +18,7 @@ export interface ENSProvider {
     getAddress(name: string): Promise<string>;
 }
 
-export interface Provider extends StorageProvider, CallProvider, CodeProvider, ENSProvider { };
+export interface Provider extends StorageProvider, CallProvider, CodeProvider, ENSProvider {};
 
 
 export interface AnyProvider { }; // TODO: Can we narrow this more?
@@ -36,7 +36,22 @@ interface EIP1193 {
 
 // Abstract away web3 provider inconsistencies
 
+function isCompatibleProvider(provider: any): boolean {
+    // FIXME: Is there a better way to use the TypeScript type system to do this?
+    // `provider isinstance Provider` does not work because Provider is an interface, not a class. Should it be?
+    return (
+        typeof provider.getStorageAt === "function" &&
+        typeof provider.call === "function" &&
+        typeof provider.getCode === "function" &&
+        typeof provider.getAddress === "function"
+    );
+}
+
 export function CompatibleProvider(provider: any): Provider {
+    if (isCompatibleProvider(provider)) {
+        // Already compatible, avoid rewrapping it
+        return provider;
+    }
     if (typeof provider.getAddress === "function") {
         return new HighLevelProvider(provider);
     }
@@ -82,14 +97,15 @@ export function CompatibleProvider(provider: any): Provider {
  * console.log(code); // "0x6001600101"
  * ```
  */
-export function WithCachedCode(provider: Provider, codeCache: Record<string, string>): Provider {
+export function WithCachedCode(provider: AnyProvider, codeCache: Record<string, string>): Provider {
+    const compatibleProvider = CompatibleProvider(provider);
     return {
-        ...provider,
+        ...compatibleProvider,
         async getCode(address: string): Promise<string> {
             if (codeCache[address]) {
                 return codeCache[address];
             }
-            return await provider.getCode(address);
+            return await compatibleProvider.getCode(address);
         }
     };
 }


Test patch:
diff --git a/src/__tests__/providers.test.ts b/src/__tests__/providers.test.ts
index 9844a91..ad173f0 100644
--- a/src/__tests__/providers.test.ts
+++ b/src/__tests__/providers.test.ts
@@ -7,7 +7,7 @@ const fakeProvider = whatsabi.providers.CompatibleProvider({
     request: () => { },
 });
 
-test('provider WithCachedCode helper', async () => {
+test('provider WithCachedCode with CompatibleProvider', async () => {
     const address = "0x0000000000000000000000000000000000000001";
     const provider = whatsabi.providers.WithCachedCode(fakeProvider, {
         [address]: "0xf00",
@@ -16,3 +16,16 @@ test('provider WithCachedCode helper', async () => {
     expect(await provider.getCode(address)).toStrictEqual("0xf00");
     expect(await provider.getCode("not cached")).toBeUndefined();
 });
+
+test('provider WithCachedCode with AnyProvider', async () => {
+    const anyProvider = {
+        request: () => { },
+    };
+    const address = "0x0000000000000000000000000000000000000001";
+    const provider = whatsabi.providers.WithCachedCode(anyProvider, {
+        [address]: "0xf00",
+    });
+
+    expect(await provider.getCode(address)).toStrictEqual("0xf00");
+    expect(await provider.getCode("not cached")).toBeUndefined();
+});