Skip to content

Add Account framework docs and guides #5660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 139 commits into
base: master
Choose a base branch
from

Conversation

ernestognw
Copy link
Member

@ernestognw ernestognw commented May 2, 2025

Check #5659 before

PR Checklist

  • Tests
  • Documentation
  • Changeset entry (run npx changeset add)

@ernestognw
Copy link
Member Author

I don't understand how skipping a contract causes an issue with that contract's import.

Skipping MyAccountERC7913 avoids replacing:

+import {Account} from "@openzeppelin/contracts/account/Account.sol";
-import {Account} from "../../.../account/Account.sol";

@Amxx
Copy link
Collaborator

Amxx commented Jun 19, 2025

I don't understand how skipping a contract causes an issue with that contract's import.

Skipping MyAccountERC7913 avoids replacing:

+import {Account} from "@openzeppelin/contracts/account/Account.sol";
-import {Account} from "../../.../account/Account.sol";

What I don't understand is that I would expect skipped contract to just not cause anything, no transpiled code generated, nothing to compile, no possible error. I was under the impression that skipping is a way around the issue, not that skipping would cause issues.

@ernestognw ernestognw requested a review from Amxx June 20, 2025 10:39
@@ -151,7 +151,7 @@ abstract contract MultiSignerERC7913 is AbstractSigner {
*
* Requirements:
*
* * The {signers}'s length must be `>=` to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not.
* * The {getSignerCount}'s length must be `>=` to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* * The {getSignerCount}'s length must be `>=` to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not.
* * The {getSignerCount} must be greater or equal than to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes the line too large, and not sure how to keep a single bullet point in 2 rows

Comment on lines 23 to 41
/// @dev Predict the address of the account
function predictAddress(bytes32 salt, bytes calldata callData) public view returns (address) {
return (_impl.predictDeterministicAddress(_saltedCallData(salt, callData), address(this)));
}

/// @dev Create clone accounts on demand
function cloneAndInitialize(bytes32 salt, bytes calldata callData) public returns (address) {
address predicted = predictAddress(salt, callData);
if (predicted.code.length == 0) {
_impl.cloneDeterministic(_saltedCallData(salt, callData));
predicted.functionCall(callData);
}
return predicted;
}

function _saltedCallData(bytes32 salt, bytes calldata callData) internal pure returns (bytes32) {
// Scope salt to the callData to avoid front-running the salt with a different callData
return keccak256(abi.encodePacked(salt, callData));
}
Copy link
Collaborator

@Amxx Amxx Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is something interresting to notice when we pass arbitrary "initialize" calldata to the factory:
If we add more stuff after the abi encoded call (like a suffix), it won't be processed by the implementation (during initialization) but if will affect the hash. So technically, we can make the salt optional here and do

Suggested change
/// @dev Predict the address of the account
function predictAddress(bytes32 salt, bytes calldata callData) public view returns (address) {
return (_impl.predictDeterministicAddress(_saltedCallData(salt, callData), address(this)));
}
/// @dev Create clone accounts on demand
function cloneAndInitialize(bytes32 salt, bytes calldata callData) public returns (address) {
address predicted = predictAddress(salt, callData);
if (predicted.code.length == 0) {
_impl.cloneDeterministic(_saltedCallData(salt, callData));
predicted.functionCall(callData);
}
return predicted;
}
function _saltedCallData(bytes32 salt, bytes calldata callData) internal pure returns (bytes32) {
// Scope salt to the callData to avoid front-running the salt with a different callData
return keccak256(abi.encodePacked(salt, callData));
}
/// @dev Predict the address of the account
function predictAddress(bytes calldata callData) public view returns (address) {
return _impl.predictDeterministicAddress(keccak256(callData), address(this));
}
/// @dev Create clone accounts on demand
function cloneAndInitialize(bytes calldata callData) public returns (address) {
address predicted = predictAddress(callData);
if (predicted.code.length == 0) {
_impl.cloneDeterministic(keccak256(callData));
predicted.functionCall(callData);
}
return predicted;
}

If someone what to use a salt, they will "just" have to call cloneAndInitialize with
abi.encodePacked(abi.encodeCall(Account.intialize, (<...args...>)), salt);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is, given that both versions have similar features, do we want:

  • a more simpler version, easy to read, but where some features are "hidden" (and optional)
  • a more complex version, harder to read, but where the extra feature are "in your face"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong preference, I think the factory is illustrative and the documentation already suggests some key considerations like "ensure the account address is deterministically tied to the initial owners". So all good with the current suggestion.

Overall I agree that an automated factory generation in Wizard must do the job better than this, and we may want to go for that option instead of a canonical factory in OZ contracts.

@ernestognw ernestognw requested a review from a team June 23, 2025 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants