Skip to content

Commit

Permalink
Merge pull request #140 from bcnmy/fix/EIP712-Test
Browse files Browse the repository at this point in the history
Fix/eip712 test
  • Loading branch information
livingrockrises authored Aug 19, 2024
2 parents ba67d11 + 742e823 commit 327e1e4
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 370 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@
"solidity-coverage": "^0.8.12",
"ts-node": ">=10.9.2",
"typechain": "^8.3.2",
"typescript": ">=5.4.5",
"viem": "^2.12.5"
"typescript": ">=5.4.5"
},
"keywords": [
"nexus",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ contract TestERC1271Account_IsValidSignature is NexusTest_Base {
}

/// @notice Tests the failure of an EIP-712 signature validation due to a wrong signer.
function test_isValidSignature_EIP712Sign_MockValidator_Wrong1271Signer_Fail() public {
function test_isValidSignature_EIP712Sign_MockValidator_Wrong1271Signer_Fail() public view {
TestTemps memory t;
t.contents = keccak256("123");
(t.v, t.r, t.s) = vm.sign(BOB.privateKey, toERC1271Hash(t.contents, payable(address(ALICE_ACCOUNT))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ contract TestModuleManager_EnableMode is Test, TestModuleManagement_Base {

bytes memory enableModeSig = signMessage(BOB, hashToSign); //should be signed by current owner
enableModeSig = abi.encodePacked(address(VALIDATOR_MODULE), enableModeSig); //append validator address

// Enable Mode Sig Prefix
// uint256 moduleTypeId
// bytes4 initDataLength
Expand Down
1 change: 0 additions & 1 deletion test/foundry/utils/NexusTest_Base.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity ^0.8.26;

import "./Imports.sol";
import "./TestHelper.t.sol";
import "./EventsAndErrors.sol";

/// @title NexusTest_Base - Base contract for testing Nexus smart account functionalities
Expand Down
9 changes: 4 additions & 5 deletions test/hardhat/common/Stakeable.specs.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { ethers } from "hardhat";
import { expect } from "chai";
import { AddressLike, parseEther } from "ethers";
import { AddressLike, parseEther, ZeroAddress } from "ethers";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { EntryPoint, Nexus, Stakeable } from "../../../typechain-types";
import { deployContractsAndSAFixture } from "../utils/deployment";
import { zeroAddress } from "viem";

describe("Stakeable tests", function () {
let smartAccount: Nexus;
Expand Down Expand Up @@ -66,19 +65,19 @@ describe("Stakeable tests", function () {

it("Should fail to add stake to an incorrect entrypoint address", async function () {
await expect(
stakeable.addStake(zeroAddress, 0, { value: parseEther("1") }),
stakeable.addStake(ZeroAddress, 0, { value: parseEther("1") }),
).to.be.revertedWithCustomError(stakeable, "InvalidEntryPointAddress");
});

it("Should fail to unlock stake from an incorrect entrypoint address", async function () {
await expect(
stakeable.unlockStake(zeroAddress),
stakeable.unlockStake(ZeroAddress),
).to.be.revertedWithCustomError(stakeable, "InvalidEntryPointAddress");
});

it("Should fail to withdraw stake from an incorrect entrypoint address", async function () {
await expect(
stakeable.withdrawStake(zeroAddress, ownerAddress),
stakeable.withdrawStake(ZeroAddress, ownerAddress),
).to.be.revertedWithCustomError(stakeable, "InvalidEntryPointAddress");
});

Expand Down
132 changes: 63 additions & 69 deletions test/hardhat/smart-account/Nexus.Basics.specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ import {
AddressLike,
Signer,
ZeroAddress,
concat,
hashMessage,
keccak256,
solidityPacked,
toBeHex,
zeroPadBytes,
} from "ethers";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import {
Expand All @@ -21,13 +18,14 @@ import {
} from "../../../typechain-types";
import { ExecutionMethod, ModuleType } from "../utils/types";
import { deployContractsAndSAFixture } from "../utils/deployment";
import { encodeData, to18 } from "../utils/encoding";
import { to18 } from "../utils/encoding";
import {
getInitCode,
buildPackedUserOp,
generateUseropCallData,
getNonce,
MODE_VALIDATION
MODE_VALIDATION,
getAccountDomainStructFields
} from "../utils/operationHelpers";
import {
CALLTYPE_BATCH,
Expand All @@ -39,7 +37,6 @@ import {
MODE_PAYLOAD,
UNUSED,
} from "../utils/erc7579Utils";
import { Hex, hashTypedData, toHex } from "viem";

describe("Nexus Basic Specs", function () {
let factory: K1ValidatorFactory;
Expand Down Expand Up @@ -135,7 +132,6 @@ describe("Nexus Basic Specs", function () {

it("Should get implementation address of smart account", async () => {
const saImplementation = await smartAccount.getImplementation();
console.log("Implementation Address: ", saImplementation);
expect(saImplementation).to.not.equal(ZeroAddress);
});

Expand All @@ -162,42 +158,6 @@ describe("Nexus Basic Specs", function () {
expect(domainSeparator).to.not.equal(ZeroAddress);
});

it("Should get hashed typed data", async () => {
const hash = hashTypedData({
domain: {
name: "Nexus",
version: "1",
chainId: 1,
verifyingContract: smartAccountAddress as Hex,
},
types: {
Person: [
{ name: "name", type: "string" },
{ name: "wallet", type: "address" },
],
Mail: [
{ name: "from", type: "Person" },
{ name: "to", type: "Person" },
{ name: "contents", type: "string" },
],
},
primaryType: "Mail",
message: {
from: {
name: "Cow",
wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
},
to: {
name: "Bob",
wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
},
contents: "Hello, Bob!",
},
});
const hashedTypedData = await smartAccount.hashTypedData(hash);
expect(hashedTypedData).to.not.be.undefined;
});

it("Should verify supported account modes", async function () {
expect(
await smartAccount.supportsExecutionMode(
Expand Down Expand Up @@ -335,7 +295,7 @@ describe("Nexus Basic Specs", function () {
// https://github.com/frangio/eip712-wrapper-for-eip1271/blob/master/src/eip1271-account.ts#L34
// https://github.com/wevm/viem/blob/main/src/actions/wallet/signMessage.ts
// https://github.com/ethers-io/ethers.js/blob/92761872198cf6c9334570da3d110bca2bafa641/src.ts/providers/provider-jsonrpc.ts#L435
it("Should check signature validity using smart account isValidSignature", async function () {
it("Should check signature validity using smart account isValidSignature for Personal Sign", async function () {
const isModuleInstalled = await smartAccount.isModuleInstalled(
ModuleType.Validation,
await validatorModule.getAddress(),
Expand All @@ -346,30 +306,12 @@ describe("Nexus Basic Specs", function () {
// 1. Convert foundry util to ts code (as below)

const data = keccak256("0x1234");

// Define constants as per the original Solidity function
const DOMAIN_NAME = "Nexus";
const DOMAIN_VERSION = "1.0.0-beta";
const DOMAIN_TYPEHASH =
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)";
const PARENT_TYPEHASH = "PersonalSign(bytes prefixed)";
const ALICE_ACCOUNT = smartAccountAddress;
const network = await ethers.provider.getNetwork();
const chainId = network.chainId;

// Calculate the domain separator
const domainSeparator = ethers.keccak256(
ethers.AbiCoder.defaultAbiCoder().encode(
["bytes32", "bytes32", "bytes32", "uint256", "address"],
[
ethers.keccak256(ethers.toUtf8Bytes(DOMAIN_TYPEHASH)),
ethers.keccak256(ethers.toUtf8Bytes(DOMAIN_NAME)),
ethers.keccak256(ethers.toUtf8Bytes(DOMAIN_VERSION)),
chainId,
ALICE_ACCOUNT,
],
),
);
const domainSeparator = await smartAccount.DOMAIN_SEPARATOR();

// Calculate the parent struct hash
const parentStructHash = ethers.keccak256(
Expand All @@ -384,11 +326,6 @@ describe("Nexus Basic Specs", function () {
ethers.concat(["0x1901", domainSeparator, parentStructHash]),
);

console.log(
"being signed",
ethers.hashMessage(ethers.getBytes(resultHash)),
);

const signature = await smartAccountOwner.signMessage(
ethers.getBytes(resultHash),
);
Expand All @@ -403,6 +340,63 @@ describe("Nexus Basic Specs", function () {

expect(isValid).to.equal("0x1626ba7e");
});

it("Should check signature validity using smart account isValidSignature for EIP 712 signature", async function () {
const PARENT_TYPEHASH = "TypedDataSign(Contents contents,bytes1 fields,string name,string version,uint256 chainId,address verifyingContract,bytes32 salt,uint256[] extensions)Contents(bytes32 stuff)";
const APP_DOMAIN_SEPARATOR = "0xa1a044077d7677adbbfa892ded5390979b33993e0e2a457e3f974bbcda53821b";
const data = "0x1234";
const contents = ethers.keccak256(ethers.toUtf8Bytes(data));

const accountDomainStructFields = await getAccountDomainStructFields(smartAccount);

const parentStructHash = ethers.keccak256(
ethers.solidityPacked(["bytes", "bytes"],[
ethers.AbiCoder.defaultAbiCoder().encode(
["bytes32", "bytes32"],
[ethers.keccak256(ethers.toUtf8Bytes(PARENT_TYPEHASH)), contents]
),
accountDomainStructFields
])
);

const dataToSign = ethers.keccak256(
ethers.concat([
'0x1901',
APP_DOMAIN_SEPARATOR,
parentStructHash
])
);

const signature = await smartAccountOwner.signMessage(ethers.getBytes(dataToSign));

const contentsType = ethers.toUtf8Bytes("Contents(bytes32 stuff)");

const signatureData = ethers.concat([
signature,
APP_DOMAIN_SEPARATOR,
contents,
contentsType,
ethers.toBeHex(contentsType.length, 2)
]);

const contentsHash = keccak256(
ethers.concat([
'0x1901',
APP_DOMAIN_SEPARATOR,
contents
])
);

const finalSignature = ethers.solidityPacked(["address", "bytes"],[
await validatorModule.getAddress(),
signatureData
]);

const isValid = await smartAccount.isValidSignature(contentsHash, finalSignature);

expect(isValid).to.equal("0x1626ba7e");
});

});

describe("Smart Account check Only Entrypoint actions", function () {
Expand Down
12 changes: 4 additions & 8 deletions test/hardhat/smart-account/Nexus.Factory.specs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ethers } from "hardhat";
import { expect } from "chai";
import { AddressLike, Signer, keccak256, solidityPacked } from "ethers";
import { AddressLike, Signer, ZeroAddress, keccak256, solidityPacked } from "ethers";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import {
K1ValidatorFactory,
Expand All @@ -12,19 +12,15 @@ import {
Bootstrap,
BootstrapLib,
MockHook,
MockExecutor,
MockHandler,
MockRegistry,
} from "../../../typechain-types";
import {
deployContractsAndSAFixture,
deployContractsFixture,
} from "../utils/deployment";
import { encodeData, to18 } from "../utils/encoding";
import { to18 } from "../utils/encoding";
import { MODE_VALIDATION, buildPackedUserOp, getNonce } from "../utils/operationHelpers";
import { BootstrapConfigStruct } from "../../../typechain-types/contracts/factory/K1ValidatorFactory";
import { toBytes, zeroAddress } from "viem";
import { GENERIC_FALLBACK_SELECTOR } from "../utils/erc7579Utils";
import { BootstrapConfigStruct } from "../../../typechain-types/contracts/lib/BootstrapLib";

describe("Nexus Factory Tests", function () {
let factory: K1ValidatorFactory;
Expand Down Expand Up @@ -349,7 +345,7 @@ describe("Nexus Factory Tests", function () {
owner,
);
await expect(
ContractFactory.deploy(zeroAddress, owner),
ContractFactory.deploy(ZeroAddress, owner),
).to.be.revertedWithCustomError(
factory,
"ImplementationAddressCanNotBeZero()",
Expand Down
3 changes: 1 addition & 2 deletions test/hardhat/smart-account/Nexus.ModuleManager.specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
UNUSED,
installModule,
} from "../utils/erc7579Utils";
import { toBytes } from "viem";

describe("Nexus Module Management Tests", () => {
let deployedNexus: Nexus;
Expand Down Expand Up @@ -632,7 +631,7 @@ describe("Nexus Module Management Tests", () => {
it("Should correctly install a fallback handler module on the smart account", async () => {
const exampleSender = await deployedNexus.getAddress();
const exampleValue = 12345;
const exampleData = toBytes("0x12345678");
const exampleData = ethers.getBytes("0x12345678");

await expect(
mockFallbackHandler.onGenericFallback(
Expand Down
18 changes: 17 additions & 1 deletion test/hardhat/utils/operationHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ethers } from "hardhat";
import { toGwei } from "./encoding";
import { ExecutionMethod, PackedUserOperation, UserOperation } from "./types";
import { Signer, AddressLike, BytesLike, BigNumberish, toBeHex, concat } from "ethers";
import { EntryPoint } from "../../../typechain-types";
import { EntryPoint, Nexus } from "../../../typechain-types";
import {
CALLTYPE_SINGLE,
EXECTYPE_DEFAULT,
Expand Down Expand Up @@ -31,6 +31,7 @@ export const DefaultsForUserOp: UserOperation = {
export const MODE_VALIDATION = "0x00";
export const MODE_MODULE_ENABLE = "0x01";

const abiCoder = new ethers.AbiCoder();
/**
* Simplifies the creation of a PackedUserOperation object by abstracting repetitive logic and enhancing readability.
* @param userOp The user operation details.
Expand Down Expand Up @@ -404,6 +405,21 @@ export async function getNonce(
return await entryPoint.getNonce(accountAddress, key);
}

export async function getAccountDomainStructFields(account: Nexus): Promise<string> {
const [fields, name, version, chainId, verifyingContract, salt, extensions] = await account.eip712Domain();
return ethers.AbiCoder.defaultAbiCoder().encode(
["bytes1", "bytes32", "bytes32", "uint256", "address", "bytes32", "bytes32"],
[
fields, // matches Solidity
ethers.keccak256(ethers.toUtf8Bytes(name)), // matches Solidity
ethers.keccak256(ethers.toUtf8Bytes(version)), // matches Solidity
chainId,
verifyingContract,
salt,
ethers.keccak256(ethers.solidityPacked(["uint256[]"], [extensions]))
]
);
}
// More functions to be added
// 1. simulateValidation (using EntryPointSimulations)
// 2. simulareHandleOps
Loading

0 comments on commit 327e1e4

Please sign in to comment.