Skip to content

Commit

Permalink
Merge 91d63e1 into 06c4aa3
Browse files Browse the repository at this point in the history
  • Loading branch information
qd-qd authored Mar 31, 2024
2 parents 06c4aa3 + 91d63e1 commit 203fc7f
Show file tree
Hide file tree
Showing 35 changed files with 587 additions and 123 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ url = git@github.com:OpenZeppelin/openzeppelin-contracts.git
[submodule "lib/webauthn.git"]
path = lib/webauthn.git
url = git@github.com:0x90d2b2b7fb7599eebb6e7a32980857d8/webauthn.git
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
18 changes: 9 additions & 9 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ fuzz = { runs = 10_000 }
verbosity = 4

[etherscan]
arbitrum_one = { key = "${API_KEY_ARBISCAN}" }
avalanche = { key = "${API_KEY_SNOWTRACE}" }
bnb_smart_chain = { key = "${API_KEY_BSCSCAN}" }
gnosis_chain = { key = "${API_KEY_GNOSISSCAN}" }
goerli = { key = "${API_KEY_ETHERSCAN}" }
mainnet = { key = "${API_KEY_ETHERSCAN}" }
optimism = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" }
polygon = { key = "${API_KEY_POLYGONSCAN}" }
sepolia = { key = "${API_KEY_ETHERSCAN}" }
# arbitrum_one = { key = "${API_KEY_ARBISCAN}" }
# avalanche = { key = "${API_KEY_SNOWTRACE}" }
# bnb_smart_chain = { key = "${API_KEY_BSCSCAN}" }
# gnosis_chain = { key = "${API_KEY_GNOSISSCAN}" }
# goerli = { key = "${API_KEY_ETHERSCAN}" }
# mainnet = { key = "${API_KEY_ETHERSCAN}" }
# optimism = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" }
# polygon = { key = "${API_KEY_POLYGONSCAN}" }
# sepolia = { key = "${API_KEY_ETHERSCAN}" }

[fmt]
bracket_spacing = true
Expand Down
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@eth-infinitism/=lib/account-abstraction/contracts/
@openzeppelin/=lib/openzeppelin-contracts/contracts/
@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
@webauthn/=lib/webauthn.git/src/
2 changes: 1 addition & 1 deletion script/Account/01_AccountDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ contract SmartAccountDeploy is BaseScript {
SmartAccount account = new SmartAccount(entryPointAddress, verifier);

// 3. Check the version of the account factory is the expected one
require(Metadata.VERSION == account.VERSION());
require(Metadata.VERSION == account.VERSION(), "Version mismatch");
return account;
}
}
33 changes: 0 additions & 33 deletions script/AccountFactory/01_FactoryDeploy.s.sol

This file was deleted.

52 changes: 52 additions & 0 deletions script/AccountFactory/01_FactoryDeployImplementation.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: APACHE-2.0
pragma solidity >=0.8.19 <0.9.0;

import { AccountFactory } from "src/v1/AccountFactory.sol";
import { SmartAccount } from "src/v1/Account/SmartAccount.sol";
import { BaseScript } from "../Base.s.sol";
import { Metadata } from "src/v1/Metadata.sol";

/// @title FactoryDeployImplementation
/// @notice Deploy an implementation of the account factory
contract FactoryDeployImplementation is BaseScript {
function run() public broadcast returns (AccountFactory) {
address payable accountImplementation = payable(vm.envAddress("ACCOUNT_IMPLEMENTATION"));

// 1. Check if the account implementation is deployed
require(address(accountImplementation).code.length > 0, "Account not deployed");

// 2. Check the version of the account is the expected one
require(Metadata.VERSION == SmartAccount(accountImplementation).VERSION(), "Version mismatch");

// 3. Confirm the account implementation address with the user
string memory prompt = string(
abi.encodePacked(
"The account implementation can never be changed in the factory contract."
" If you would like to update it later on, consider proxing the account implementaton",
" contract and passing the proxy address as the account implementation.",
"\n\n",
"Are you sure you want to use the following contract? (yes for approval): ",
vm.toString(accountImplementation)
)
);
try vm.prompt(prompt) returns (string memory res) {
// solhint-disable-next-line custom-errors
// forgefmt: disable-next-item
require(
keccak256(abi.encodePacked(res)) == keccak256(abi.encodePacked("yes")),
"Script aborted by the user"
);
} catch (bytes memory) {
// solhint-disable-next-line custom-errors
revert("Entrypoint address not approved");
}

// 4. Deploy the account factory
AccountFactory accountFactoryImplementation = new AccountFactory(accountImplementation);

// 5. Check the version of the account factory is the expected one
require(Metadata.VERSION == accountFactoryImplementation.version(), "Version mismatch");

return accountFactoryImplementation;
}
}
40 changes: 40 additions & 0 deletions script/AccountFactory/02_FactoryDeployInstance.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: APACHE-2.0
pragma solidity >=0.8.19 <0.9.0;

import { AccountFactory } from "src/v1/AccountFactory.sol";
import { BaseScript } from "../Base.s.sol";
import { TransparentUpgradeableProxy } from "src/v1/Proxy/TransparentProxy.sol";

bytes32 constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

/// @title FactoryDeployInstance
/// @notice Deploy an instance of the account factory
contract FactoryDeployInstance is BaseScript {
function run() public broadcast returns (AccountFactory accountFactory, address proxyAdmin) {
// 1. check the proxy owner is valid
address proxyOwner = vm.envAddress("PROXY_OWNER");
require(proxyOwner != address(0), "Invalid owner address");

// 2. check the factory signer is valid
address factorySigner = vm.envAddress("FACTORY_SIGNER");
require(factorySigner != address(0), "Invalid factory signer address");

// 3. check the factory implementation is valid
address payable factoryImplementation = payable(vm.envAddress("FACTORY_IMPLEMENTATION"));
require(address(factoryImplementation).code.length > 0, "Factory implem' not deployed");

// 4. Deploy a instance of the factory
accountFactory = AccountFactory(
address(
new TransparentUpgradeableProxy(
factoryImplementation,
proxyOwner,
abi.encodeWithSelector(AccountFactory.initialize.selector, factorySigner)
)
)
);

// 5. fetch the proxy admin
proxyAdmin = abi.decode(abi.encodePacked(vm.load(address(accountFactory), ADMIN_SLOT)), (address));
}
}
23 changes: 23 additions & 0 deletions script/AccountFactory/03_FactoryGetProxyAdminInformation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: APACHE-2.0
pragma solidity >=0.8.19 <0.9.0;

import { BaseScript } from "../Base.s.sol";
import { ProxyAdmin } from "src/v1/Proxy/TransparentProxy.sol";

bytes32 constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

/// @title FactoryGetProxyAdminInformation
/// @notice Fetch the proxy admin and the owner of the proxy admin
contract FactoryGetProxyAdminInformation is BaseScript {
function run() public broadcast returns (address proxyAdmin, address proxyOwner) {
// 1. check the factory implementation is valid
address payable factoryInstance = payable(vm.envAddress("FACTORY_INSTANCE"));
require(address(factoryInstance).code.length > 0, "Factory implem' not deployed");

// 2. fetch the proxy admin
proxyAdmin = abi.decode(abi.encodePacked(vm.load(factoryInstance, ADMIN_SLOT)), (address));

// 3. fetch the proxy owner
proxyOwner = ProxyAdmin(proxyAdmin).owner();
}
}
6 changes: 3 additions & 3 deletions script/Paymaster/01_PaymasterDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ contract PaymasterDeploy is BaseScript {
// forgefmt: disable-next-item
require(
keccak256(abi.encodePacked(res)) == keccak256(abi.encodePacked("yes")),
"Entrypoint address not approved"
"Entrypoint not approved"
);
} catch (bytes memory) {
// solhint-disable-next-line custom-errors
revert("Entrypoint address not approved");
}

// 2. Check if the address of the entryPoint is deployed
require(entryPointAddress.code.length > 0, "The entrypoint is not deployed");
require(entryPointAddress.code.length > 0, "Entrypoint not deployed");

// 3. Run the script using the entrypoint address
return run(entryPointAddress);
Expand All @@ -54,7 +54,7 @@ contract PaymasterDeploy is BaseScript {
Paymaster paymaster = new Paymaster(entryPointAddress, owner, operator);

// 4. Check the version of the paymaster is the expected one
require(Metadata.VERSION == paymaster.VERSION());
require(Metadata.VERSION == paymaster.VERSION(), "Version mismatch");

return paymaster;
}
Expand Down
4 changes: 2 additions & 2 deletions script/utils/Version.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ contract SmoothVersionScan is BaseScript {
address smoothContract = vm.envAddress("SMOOTH_CONTRACT");

// 1. get full version of the contract
(bool success, bytes memory data) = smoothContract.call(abi.encodeWithSignature("VERSION()"));
require(success, "Failed to get the version of the contract");
(bool success, bytes memory data) = smoothContract.call(abi.encodeWithSignature("version()"));
require(success, "Failed to fetch version");
uint256 fullVersion = abi.decode(data, (uint256));

// 2. parse major/minor/patch versions of the contract
Expand Down
53 changes: 31 additions & 22 deletions src/v1/AccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
pragma solidity >=0.8.20 <0.9.0;

import { ERC1967Proxy } from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
import { Ownable } from "@openzeppelin/access/Ownable.sol";
import "src/utils/Signature.sol" as Signature;
import { OwnableUpgradeable } from "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin-upgradeable/proxy/utils/Initializable.sol";
import { SmartAccount } from "./Account/SmartAccount.sol";
import { Metadata } from "src/v1/Metadata.sol";
import { SignerVaultWebAuthnP256R1 } from "src/utils/SignerVaultWebAuthnP256R1.sol";
import "src/utils/Signature.sol" as Signature;

// FIXME: createAndInitAccount() Understand the implications of the ban system of the function

Expand All @@ -20,13 +21,7 @@ import { SignerVaultWebAuthnP256R1 } from "src/utils/SignerVaultWebAuthnP256R1.s
/// signed by the owner. The message is the keccak256 hash of the login of the account.
/// As the address of the account is already dependant of the address of the factory, we do not need to
/// include it in the signature.
contract AccountFactory is Ownable {
// ==============================
// ========= METADATA ===========
// ==============================

uint256 public constant VERSION = Metadata.VERSION;

contract AccountFactory is Initializable, OwnableUpgradeable {
// ==============================
// ========= CONSTANT ===========
// ==============================
Expand All @@ -40,23 +35,30 @@ contract AccountFactory is Ownable {
event AccountCreated(address account, bytes authenticatorData);

error InvalidSignature(address accountAddress, bytes authenticatorData, bytes signature);
error InvalidAccountImplementation();

// ==============================
// ======= CONSTRUCTION =========
// ==============================

/// @notice Deploy the implementation of the account and store it in the storage of the factory. This
/// implementation will be used as the implementation reference for all the proxies deployed by this
/// factory. To make sure the instance deployed cannot be used, we brick it by calling the `initialize`
/// function and setting an invalid first signer.
/// @param owner The address used to verify the signature. It is the owner of the factory.
/// @param _accountImplementation The address of the implementation of the smart account.
/// @dev The account deployed here is expected to be proxied later, its own storage won't be used.
/// All the arguments passed to the constructor function are used to set immutable variables.
/// The account deployed is expected to be bricked by the `initialize` function.
constructor(address owner, address _accountImplementation) Ownable(owner) {
// 1. set the address of the implementation account
/// factory.
/// @param _accountImplementation The address of the implementation of the smart account. Must never be changed!!
/// @dev All the arguments passed to the constructor function are used to set immutable variables.
constructor(address _accountImplementation) {
// 1. set the address of the implementation account -- THIS ADDRESS MUST NEVER BE CHANGED (!!)
if (_accountImplementation == address(0)) revert InvalidAccountImplementation();
accountImplementation = payable(_accountImplementation);

// 2. prevent the implementation contract from being used directly
_disableInitializers();
}

// The initialize function will be used to set up the initial state of the contract.
function initialize(address owner) public virtual reinitializer(1) {
// 1. set the owner
__Ownable_init(owner);
}

// ==============================
Expand All @@ -77,6 +79,7 @@ contract AccountFactory is Ownable {
)
internal
view
virtual
returns (bool)
{
// 1. Recreate the message signed by the operator (owner)
Expand All @@ -91,12 +94,13 @@ contract AccountFactory is Ownable {
/// @param authenticatorData The authenticatorData field of the WebAuthn response when creating a signer
/// @param signature Signature made off-chain by made the operator of the factory (owner). It gates the use of the
/// factory.
/// @return The address of the existing account (either deployed by this fucntion or not)
/// @return The address of the existing account (either deployed by this function or not)
function createAndInitAccount(
bytes calldata authenticatorData,
bytes calldata signature
)
external
virtual
returns (address)
{
// 1. calculate the salt that will be used to deploy the account
Expand Down Expand Up @@ -133,7 +137,7 @@ contract AccountFactory is Ownable {

/// @notice This function calculates the salt used to deploy the account
/// @dev The salt is calculated using the credIdHash, the pubkeyX and the pubkeyY extracted from the
/// authenticatorData
/// authenticatorData. This function must never be changed (!!)
/// @param authenticatorData The authenticatorData field of the WebAuthn response when creating a signer
function calculateSalt(bytes calldata authenticatorData) internal pure returns (bytes32) {
// 1. extract the signer from the authenticatorData
Expand All @@ -145,7 +149,7 @@ contract AccountFactory is Ownable {
}

/// @notice This utility function returns the address of the account that would be deployed using the salt
/// @dev This is the under the hood formula used by the CREATE2 opcode
/// @dev This is the under the hood formula used by the CREATE2 opcode. This function must never be changed (!!)
/// @param salt The salt used to deploy the account
/// @return The address of the account that would be deployed
function getAddress(bytes32 salt) internal view returns (address) {
Expand Down Expand Up @@ -175,13 +179,18 @@ contract AccountFactory is Ownable {
}

/// @notice This utility function returns the address of the account that would be deployed using the authData
/// @dev The salt is calculated using the signer extracted from the authenticatorData
/// @dev The salt is calculated using the signer extracted from the authenticatorData. This function must never
/// be changed (!!)
/// @param authenticatorData The authenticatorData field of the WebAuthn response when creating a signer
/// @return The address of the account that would be deployed
function getAddress(bytes calldata authenticatorData) external view returns (address) {
bytes32 salt = calculateSalt(authenticatorData);
return getAddress(salt);
}

function version() external pure virtual returns (uint256) {
return Metadata.VERSION;
}
}

// NOTE:
Expand Down
8 changes: 8 additions & 0 deletions src/v1/Proxy/TransparentProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.19 <0.9.0;

import {
ProxyAdmin,
ITransparentUpgradeableProxy,
TransparentUpgradeableProxy
} from "@openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol";
3 changes: 2 additions & 1 deletion test/BaseTest/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { MessageHashUtils } from "@openzeppelin/utils/cryptography/MessageHashUt
import "src/utils/Signature.sol" as Signature;
import { BaseTestUtils } from "test/BaseTest/BaseTestUtils.sol";
import { BaseTestCreateFixtures } from "test/BaseTest/BaseTestCreateFixtures.sol";
import { BaseTestDeployment } from "test/BaseTest/BaseTestDeployment.sol";

/// @title BaseTest
/// @notice This contract override the default Foundry's `Test` contract with some utility functions
contract BaseTest is Test, BaseTestUtils, BaseTestCreateFixtures {
contract BaseTest is Test, BaseTestUtils, BaseTestCreateFixtures, BaseTestDeployment {
// solhint-disable-next-line var-name-mixedcase
VmSafe.Wallet internal SMOOTH_SIGNER;

Expand Down
Loading

0 comments on commit 203fc7f

Please sign in to comment.