Skip to content

Commit

Permalink
[SDK] Refactor: More Viem to Ox migration (#5790)
Browse files Browse the repository at this point in the history
TOOL-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on migrating functionality in the `thirdweb` package from `viem` to `ox`, improving consistency and possibly performance. It updates various files to utilize `ox` components, enhancing the codebase's integration with the `ox` library.

### Detailed summary
- Migrated `maxUint256` imports from `viem` to `ox/Solidity` in multiple files.
- Updated the `isErc6492Signature` function to use `ox__Hex.slice`.
- Changed `decodeAbiParameters` to `ox__AbiParameters.decode`.
- Replaced various utility functions with their `ox` equivalents across different modules.
- Enhanced the `verifyHash` function to utilize `ox__Signature`.
- Added new tests for `verifyContractWalletSignature` using `smartWallet`.
- Updated the workflow file to include new pull request types.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
  • Loading branch information
gregfromstl committed Jan 9, 2025
1 parent bfc4778 commit 505a026
Show file tree
Hide file tree
Showing 25 changed files with 205 additions and 270 deletions.
5 changes: 5 additions & 0 deletions .changeset/hip-llamas-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": patch
---

Migrated underlying functionality to Ox
2 changes: 1 addition & 1 deletion .github/workflows/auto-assign.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Auto Author Assign

on:
pull_request:
types: [opened, reopened]
types: [opened, reopened, ready_for_review, draft]

permissions:
pull-requests: write
Expand Down
4 changes: 2 additions & 2 deletions packages/thirdweb/src/auth/is-erc6492-signature.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sliceHex } from "viem";
import * as ox__Hex from "ox/Hex";
import type { Hex } from "../utils/encoding/hex.js";
import { ERC_6492_MAGIC_VALUE } from "./constants.js";

Expand All @@ -19,5 +19,5 @@ import { ERC_6492_MAGIC_VALUE } from "./constants.js";
* @auth
*/
export function isErc6492Signature(signature: Hex): boolean {
return sliceHex(signature, -32) === ERC_6492_MAGIC_VALUE;
return ox__Hex.slice(signature, -32) === ERC_6492_MAGIC_VALUE;
}
14 changes: 10 additions & 4 deletions packages/thirdweb/src/auth/parse-erc6492-signature.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { decodeAbiParameters, isErc6492Signature } from "viem";
import * as ox__AbiParameters from "ox/AbiParameters";
import * as ox__Address from "ox/Address";
import { WrappedSignature as ox__WrappedSignature } from "ox/erc6492";
import type { Hex } from "../utils/encoding/hex.js";
import type { OneOf } from "../utils/type-utils.js";
import type { Erc6492Signature } from "./types.js";
Expand Down Expand Up @@ -29,13 +31,17 @@ export type ParseErc6492SignatureReturnType = OneOf<
export function parseErc6492Signature(
signature: Hex,
): ParseErc6492SignatureReturnType {
if (!isErc6492Signature(signature)) {
if (!ox__WrappedSignature.validate(signature)) {
return { signature };
}

const [address, data, originalSignature] = decodeAbiParameters(
const [address, data, originalSignature] = ox__AbiParameters.decode(
[{ type: "address" }, { type: "bytes" }, { type: "bytes" }],
signature,
);
return { address: address, data, signature: originalSignature };
return {
address: ox__Address.checksum(address),
data,
signature: originalSignature,
};
}
36 changes: 16 additions & 20 deletions packages/thirdweb/src/auth/verify-hash.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import {
type Signature,
encodeDeployData,
encodeFunctionData,
isErc6492Signature,
serializeSignature,
universalSignatureValidatorAbi,
universalSignatureValidatorByteCode,
} from "viem";
import * as ox__Abi from "ox/Abi";
import * as ox__AbiConstructor from "ox/AbiConstructor";
import * as ox__AbiFunction from "ox/AbiFunction";
import * as ox__Signature from "ox/Signature";
import { WrappedSignature as ox__WrappedSignature } from "ox/erc6492";
import type { Chain } from "../chains/types.js";
import type { ThirdwebClient } from "../client/client.js";
import { type ThirdwebContract, getContract } from "../contract/contract.js";
Expand All @@ -20,7 +16,7 @@ import { serializeErc6492Signature } from "./serialize-erc6492-signature.js";

export type VerifyHashParams = {
hash: Hex;
signature: string | Uint8Array | Signature;
signature: string | Uint8Array | ox__Signature.Signature;
address: string;
client: ThirdwebClient;
chain: Chain;
Expand Down Expand Up @@ -71,7 +67,7 @@ export async function verifyHash({
const signatureHex = (() => {
if (isHex(signature)) return signature;
if (typeof signature === "object" && "r" in signature && "s" in signature)
return serializeSignature(signature);
return ox__Signature.toHex(signature);
if (signature instanceof Uint8Array) return fromBytes(signature, "hex");
// We should never hit this but TS doesn't know that
throw new Error(
Expand All @@ -85,7 +81,7 @@ export async function verifyHash({
if (!accountFactory) return signatureHex;

// If this sigature was already wrapped for ERC-6492, carry on
if (isErc6492Signature(signatureHex)) return signatureHex;
if (ox__WrappedSignature.validate(signatureHex)) return signatureHex;

Check warning on line 84 in packages/thirdweb/src/auth/verify-hash.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/auth/verify-hash.ts#L84

Added line #L84 was not covered by tests

// Otherwise, serialize the signature for ERC-6492 validation
return serializeErc6492Signature({
Expand All @@ -100,23 +96,23 @@ export async function verifyHash({
data: Hex;
};
const zkSyncChain = await isZkSyncChain(chain);
const abi = ox__Abi.from(ox__WrappedSignature.universalSignatureValidatorAbi);
if (zkSyncChain) {
// zksync chains dont support deploying code with eth_call
// need to call a deployed contract instead
verificationData = {
to: ZKSYNC_VALIDATOR_ADDRESS,
data: encodeFunctionData({
abi: universalSignatureValidatorAbi,
functionName: "isValidSig",
args: [address, hash, wrappedSignature],
}),
data: ox__AbiFunction.encodeData(
ox__AbiFunction.fromAbi(abi, "isValidSig"),
[address, hash, wrappedSignature],
),

Check warning on line 108 in packages/thirdweb/src/auth/verify-hash.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/auth/verify-hash.ts#L105-L108

Added lines #L105 - L108 were not covered by tests
};
} else {
const validatorConstructor = ox__AbiConstructor.fromAbi(abi);
verificationData = {
data: encodeDeployData({
abi: universalSignatureValidatorAbi,
data: ox__AbiConstructor.encode(validatorConstructor, {
args: [address, hash, wrappedSignature],
bytecode: universalSignatureValidatorByteCode,
bytecode: ox__WrappedSignature.universalSignatureValidatorBytecode,
}),
};
}
Expand Down
50 changes: 48 additions & 2 deletions packages/thirdweb/src/auth/verify-signature.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import * as ox__Bytes from "ox/Bytes";
import { describe, expect, it, test } from "vitest";
import { FORKED_ETHEREUM_CHAIN } from "../../test/src/chains.js";
import { TEST_CLIENT } from "../../test/src/test-clients.js";
import { TEST_ACCOUNT_A } from "../../test/src/test-wallets.js";
import { mainnet } from "../chains/chain-definitions/ethereum.js";
import { ethereum, mainnet } from "../chains/chain-definitions/ethereum.js";
import { sepolia } from "../chains/chain-definitions/sepolia.js";
import { verifyEOASignature, verifySignature } from "./verify-signature.js";
import { smartWallet } from "../wallets/smart/smart-wallet.js";
import {
verifyContractWalletSignature,
verifyEOASignature,
verifySignature,
} from "./verify-signature.js";

describe("verifyEOASignature", () => {
test("should return true for a valid signature", async () => {
Expand Down Expand Up @@ -98,3 +104,43 @@ describe.runIf(process.env.TW_SECRET_KEY)(
});
},
);

describe.runIf(process.env.TW_SECRET_KEY)(
"verifyContractWalletSignature",
async () => {
const message = "Hakuna matata";
const wallet = smartWallet({
chain: ethereum,
gasless: true,
});
const smartAccount = await wallet.connect({
client: TEST_CLIENT,
personalAccount: TEST_ACCOUNT_A,
});

test("should verify a smart account signature", async () => {
const rawSignature = await smartAccount.signMessage({ message });
const result = await verifyContractWalletSignature({
signature: rawSignature,
message,
address: smartAccount.address,
chain: ethereum,
client: TEST_CLIENT,
});
expect(result).toBe(true);
});

test("should verify a smart account signature as bytes", async () => {
const rawSignature = await smartAccount.signMessage({ message });
const bytesSignature = ox__Bytes.fromHex(rawSignature);
const result = await verifyContractWalletSignature({
signature: bytesSignature,
message,
address: smartAccount.address,
chain: ethereum,
client: TEST_CLIENT,
});
expect(result).toBe(true);
});
},
);
31 changes: 24 additions & 7 deletions packages/thirdweb/src/auth/verify-signature.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import { type SignableMessage, type Signature, recoverAddress } from "viem";
import * as ox__Bytes from "ox/Bytes";
import * as ox__Secp256k1 from "ox/Secp256k1";
import * as ox__Signature from "ox/Signature";
import type { Chain } from "../chains/types.js";
import type { ThirdwebClient } from "../client/client.js";
import { type Hex, isHex } from "../utils/encoding/hex.js";
import { hashMessage } from "../utils/hashing/hashMessage.js";
import type { Prettify } from "../utils/type-utils.js";
import { verifyHash } from "./verify-hash.js";

type Message = Prettify<
| string
| {
raw: Hex | Uint8Array;
}
>;

/**
* @auth
*/
export type VerifyEOASignatureParams = {
message: string | SignableMessage;
signature: string | Uint8Array | Signature;
message: string | Message;
signature: string | Uint8Array;
address: string;
};

Expand Down Expand Up @@ -39,9 +48,9 @@ export async function verifyEOASignature(options: VerifyEOASignatureParams) {
return false;
}

const recoveredAddress = await recoverAddress({
hash: messageHash,
signature: options.signature,
const recoveredAddress = ox__Secp256k1.recoverAddress({
payload: messageHash,
signature: ox__Signature.fromHex(options.signature),
});

if (recoveredAddress.toLowerCase() === options.address.toLowerCase()) {
Expand Down Expand Up @@ -103,9 +112,17 @@ export async function verifyContractWalletSignature({
accountFactory,
}: VerifyContractWalletSignatureParams) {
const messageHash = hashMessage(message);

const parsedSignature = (() => {
if (ox__Bytes.validate(signature)) {
return ox__Bytes.toHex(signature);
}
return signature;
})();

return verifyHash({
hash: messageHash,
signature,
signature: parsedSignature,
address,
client,
chain,
Expand Down
16 changes: 9 additions & 7 deletions packages/thirdweb/src/auth/verify-typed-data.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import type { Signature, TypedData, TypedDataDefinition } from "viem";
import { hashTypedData } from "viem";
import type * as ox__Signature from "ox/Signature";
import * as ox__TypedData from "ox/TypedData";
import type { Chain } from "../chains/types.js";
import type { ThirdwebClient } from "../client/client.js";
import type { Hex } from "../utils/encoding/hex.js";
import type { HashTypedDataParams } from "../utils/hashing/hashTypedData.js";
import { type VerifyHashParams, verifyHash } from "./verify-hash.js";

export type VerifyTypedDataParams<
typedData extends TypedData | Record<string, unknown> = TypedData,
typedData extends
| ox__TypedData.TypedData
| Record<string, unknown> = ox__TypedData.TypedData,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
> = Omit<VerifyHashParams, "hash"> &
TypedDataDefinition<typedData, primaryType> & {
ox__TypedData.Definition<typedData, primaryType> & {
address: string;
signature: string | Uint8Array | Signature;
signature: string | Uint8Array | ox__Signature.Signature;
client: ThirdwebClient;
chain: Chain;
accountFactory?: {
Expand Down Expand Up @@ -80,7 +82,7 @@ export type VerifyTypedDataParams<
* @auth
*/
export async function verifyTypedData<
typedData extends TypedData | Record<string, unknown>,
typedData extends ox__TypedData.TypedData | Record<string, unknown>,
primaryType extends keyof typedData | "EIP712Domain",
>({
address,
Expand All @@ -93,7 +95,7 @@ export async function verifyTypedData<
primaryType,
types,
}: VerifyTypedDataParams<typedData, primaryType>): Promise<boolean> {
const messageHash = hashTypedData({
const messageHash = ox__TypedData.getSignPayload({
message,
domain,
primaryType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { getContractAddress } from "viem";
import * as ox__ContractAddress from "ox/ContractAddress";
import { getGasPrice } from "../../../gas/get-gas-price.js";
import { eth_getBalance } from "../../../rpc/actions/eth_getBalance.js";
import { eth_sendRawTransaction } from "../../../rpc/actions/eth_sendRawTransaction.js";
import { getRpcClient } from "../../../rpc/rpc.js";
import { sendTransaction } from "../../../transaction/actions/send-transaction.js";
import { waitForReceipt } from "../../../transaction/actions/wait-for-tx-receipt.js";
import { prepareTransaction } from "../../../transaction/prepare-transaction.js";
import { getAddress } from "../../../utils/address.js";
import { isEIP155Enforced } from "../../../utils/any-evm/is-eip155-enforced.js";
import { getKeylessTransaction } from "../../../utils/any-evm/keyless-transaction.js";
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
Expand Down Expand Up @@ -226,15 +227,15 @@ async function _getCreate2FactoryDeploymentInfo(
},
signature: SIGNATURE,
});
const create2FactoryAddress = getContractAddress({
const create2FactoryAddress = ox__ContractAddress.from({
from: deploymentTransaction.signerAddress,
nonce: 0n,
});

return {
...deploymentTransaction,
valueToSend: gasPrice * gas,
predictedAddress: create2FactoryAddress,
predictedAddress: getAddress(create2FactoryAddress),
};
}

Expand Down
23 changes: 8 additions & 15 deletions packages/thirdweb/src/contract/verification/constructor-params.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Abi } from "abitype";
import { decodeAbiParameters } from "viem";
import type * as ox__Abi from "ox/Abi";
import * as ox__AbiConstructor from "ox/AbiConstructor";
import * as ox__AbiParameters from "ox/AbiParameters";
import { eth_getTransactionByHash } from "../../rpc/actions/eth_getTransactionByHash.js";
import { getRpcClient } from "../../rpc/rpc.js";
import type { ThirdwebContract } from "../contract.js";
Expand All @@ -10,19 +11,9 @@ type FetchConstructorParamsOptions = {
contract: ThirdwebContract;
explorerApiUrl: string;
explorerApiKey: string;
abi: Abi;
abi: ox__Abi.Abi;
};

// TODO: move to abi helpers (?)
function extractConstructorParamsFromAbi(abi: Abi) {
for (const input of abi) {
if (input.type === "constructor") {
return input.inputs || [];
}
}
return [];
}

const RequestStatus = {
OK: "1",
NOTOK: "0",
Expand All @@ -37,7 +28,8 @@ const RequestStatus = {
export async function fetchConstructorParams(
options: FetchConstructorParamsOptions,
): Promise<string> {
const constructorParamTypes = extractConstructorParamsFromAbi(options.abi);
const abiConstructor = ox__AbiConstructor.fromAbi(options.abi);
const constructorParamTypes = ox__AbiParameters.from(abiConstructor.inputs);
if (constructorParamTypes.length === 0) {
return "";
}
Expand Down Expand Up @@ -114,7 +106,8 @@ export async function fetchConstructorParams(
try {
// sanity check that the constructor params are valid
// TODO: should we sanity check after each attempt?
decodeAbiParameters(constructorParamTypes, `0x${constructorArgs}`);

ox__AbiParameters.decode(constructorParamTypes, `0x${constructorArgs}`);
} catch {
throw new Error(
"Verifying this contract requires it to be published. Run `npx thirdweb publish` to publish this contract, then try again.",
Expand Down
Loading

0 comments on commit 505a026

Please sign in to comment.