Skip to content
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

Improve NatSpec documentation #17

Closed
wants to merge 8 commits into from
Closed

Improve NatSpec documentation #17

wants to merge 8 commits into from

Conversation

xenoliss
Copy link
Contributor

@xenoliss xenoliss commented Mar 6, 2024

This PR generally improves NatSpec doc on the contracts defined in src/.

/// @notice ERC4337-compatible smart contract wallet, based on Solady ERC4337 account implementation
/// with inspiration from Alchemy's LightAccount and Daimo's DaimoAccount
/// with inspiration from Alchemy's LightAccount and Daimo's DaimoAccount.
Copy link
Contributor

Choose a reason for hiding this comment

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

is this auto formatted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No it's not (i don't think there is auto-formating for that). It's the format I'm use to but I can change it back to start from the beginning of the line.

Copy link
Contributor

Choose a reason for hiding this comment

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

Honestly, I think I prefer the indents. It's new to me too, but it looks pretty clean.

/// @param wrappedSignatureBytes The abi encoded `SignatureWrapper` struct.
///
/// @return `true` if the signature verification succeeded, else `false`.
function _validateSignature(bytes32 message, bytes calldata wrappedSignatureBytes)
Copy link
Contributor

Choose a reason for hiding this comment

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

I found wrappedSignatureBytes more clear. What's the thought with this change? I guess now we have the natspec

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed it to match with the function prototype defined in ERC1271.

/// @author Coinbase (https://github.com/coinbase/smart-wallet)
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/ERC4337Factory.sol)
contract CoinbaseSmartWalletFactory {
/// @dev Address of the ERC4337 implementation.
/// @notice Address of the ERC-4337 implementation used to deploy new cloned accounts.
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm it's not really used to deploy them?

Suggested change
/// @notice Address of the ERC-4337 implementation used to deploy new cloned accounts.
/// @notice Address of the ERC-4337 implementation used as implementation for new accounts.

///
/// @dev The account is deployed behind a minimal ERC1967 proxy whose implementation points to
/// the registered ERC-4337 `implementation`.
/// @dev The `owners` parameter is a set of addresses and/or public keys depending on the signature
Copy link
Contributor

Choose a reason for hiding this comment

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

I find this comment confusing, what does 1271 or webauthn have to do with it?

I think we can just say "an array of initial owners of the CoinbaseSmartWallet"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well I was not sure between keeping things generic or giving the details. I picked the later approach as the contracts are already tightly coupled and if you provide "an array of initial owners of the CoinbaseSmartWallet" without taking care of what they are it might revert.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think I prefer this expanded description. As a new reader, when I see bytes[] calldata arg in a function declaration, it always begs the question "what's in there?". This description offers threads that can be pulled. I think this is especially relevant and important for public/external methods where a new reader might be starting a fresh read.

/// @dev Returns the deterministic address of the account created via `createAccount`.
/// @notice Returns the deterministic address of the account created via `createAccount()`.
///
/// @param owners The initial set of owners that was / will be provided to `createAccount()`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// @param owners The initial set of owners that was / will be provided to `createAccount()`.
/// @param owners The initial set of owners provided to `createAccount()`.

Copy link
Contributor

Choose a reason for hiding this comment

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

not sure but feels a little cleaer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not opposed although one could interpret yours as "you must have already created your account"

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the tense is important to keep consistent. Most of this nat spec is written in the present tense

Copy link
Contributor

Choose a reason for hiding this comment

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

heh sorry @stevieraykatz so which is this an argument for, my edit?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes! I think this qualifies as present tense despite the past-tense "provided"

/// Based on Solady (https://github.com/vectorized/solady/blob/main/src/accounts/ERC1271.sol)
/// @title ERC-1271 With Cross Account Replay Protection
///
/// @notice Abstract ERC-1271 implementation (based on Solady's one) with guards to handle the same
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// @notice Abstract ERC-1271 implementation (based on Solady's one) with guards to handle the same
/// @notice Abstract ERC-1271 implementation (based on Solady's) with guards to handle the same

/// @title ERC-1271 With Cross Account Replay Protection
///
/// @notice Abstract ERC-1271 implementation (based on Solady's one) with guards to handle the same
/// signer being used on multiple accounts, using a nested approach based on EIP-712.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// signer being used on multiple accounts, using a nested approach based on EIP-712.
/// signer being used on multiple accounts.

///
/// keccak256("\x19\x01" || someDomainSeparator || hashStruct(someStruct))
/// The OSH must either be:
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not signed, it's just "original hash"

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure the abbreviations are making things more clear.

/// signer being used on multiple accounts, using a nested approach based on EIP-712.
///
/// @dev To prevent the same signature from being validated on different accounts owned by the samer signer,
/// we introduce an anti cross-account-replay layer: the original signed hash (OSH) is wrapped in a
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// we introduce an anti cross-account-replay layer: the original signed hash (OSH) is wrapped in a
/// we introduce an anti cross-account-replay layer: the original hash is input into a new EIP-712-compliant hash. The domain separator of this outer hash contains the chain id and address of this contract, so that it cannot be used on two accounts.

/// we introduce an anti cross-account-replay layer: the original signed hash (OSH) is wrapped in a
/// `CoinbaseSmartWalletMessage` (CSWM) struct, which is itself hashed using EIP-712.
///
/// When hashing the CSWM, the `domainSeparator` used has its `verifyingContract` field set to the address
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can simplify, see above.

/// @dev Mapping of indices to raw owner bytes, used to idenfitied owners by their
/// uint256 id (to economize calldata).
/// NOTE: `uint256` rather than a smaller uint because it provides flexibility,
/// like never run out of owner indexes (practically), at little to no cost.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think using "like" is very formal/good for this context.

mapping(bytes => bool) isOwner;
/// @dev Mapping of indices to raw owner bytes, used to idenfitied owners by their
/// uint256 id (to economize calldata).
/// NOTE: `uint256` rather than a smaller uint because it provides flexibility,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// NOTE: `uint256` rather than a smaller uint because it provides flexibility,
/// NOTE: `uint256` rather than a smaller uint because it provides flexibility, in that we will practically never run out of indices

/// uint256 id (to economize calldata).
/// NOTE: `uint256` rather than a smaller uint because it provides flexibility,
/// like never run out of owner indexes (practically), at little to no cost.
/// Furthermore on L2, where calldata gas is a concern we should not be charged
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not a "furthermore" This is explaining the "no cost" in the previous

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I saw it as an additional justification to why we picked uint256 and not uint8: because anyway L2s don't charge the 0s.

Copy link
Contributor

Choose a reason for hiding this comment

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

I actually think the whole statement should be removed. 0 bytes == 0 cost doesn't really add any helpful context.

Copy link
Contributor Author

@xenoliss xenoliss Mar 7, 2024

Choose a reason for hiding this comment

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

I agree with @stevieraykatz view on that, I don't think it brings any helpful indication.

/// Furthermore on L2, where calldata gas is a concern we should not be charged
/// for the extra 0 bytes.
///
/// In the context of checking whether something was signed by an owner this
Copy link
Contributor

Choose a reason for hiding this comment

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

I find this more confusing than what I had before. We should leave with the motivation for using indices at all.

/// @author Coinbase (https://github.com/coinbase/smart-wallet)
contract MultiOwnable {
/// keccak256(abi.encode(uint256(keccak256("coinbase.storage.MultiOwnable")) - 1)) & ~bytes32(uint256(0xff))
/// @dev Slot or the `MultiOwnableStorage` struct in storage.
Copy link
Contributor

Choose a reason for hiding this comment

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

we could mention is erc7201 compliant

/// @notice Helper function to get a storage reference to the `MultiOwnableStorage` struct.
///
/// @return $ A storage reference to the `MultiOwnableStorage` struct.
function _$() internal pure returns (MultiOwnableStorage storage $) {
Copy link
Contributor

Choose a reason for hiding this comment

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

is this idiomatic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure. I personally use _s() and s but I saw you were already using _$() as naming for the slot ( and I find _getMultiOwnableStorage() a bit long to write each time)

Copy link
Contributor

Choose a reason for hiding this comment

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

Though definitely shorter, if we're claiming to follow ERC-7201 I think we should follow all of its conventions.

/// @notice ERC4337-compatible smart contract wallet, based on Solady ERC4337 account implementation
/// with inspiration from Alchemy's LightAccount and Daimo's DaimoAccount
/// with inspiration from Alchemy's LightAccount and Daimo's DaimoAccount.
Copy link
Contributor

Choose a reason for hiding this comment

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

Honestly, I think I prefer the indents. It's new to me too, but it looks pretty clean.

///
/// @dev The account is deployed behind a minimal ERC1967 proxy whose implementation points to
/// the registered ERC-4337 `implementation`.
/// @dev The `owners` parameter is a set of addresses and/or public keys depending on the signature
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I prefer this expanded description. As a new reader, when I see bytes[] calldata arg in a function declaration, it always begs the question "what's in there?". This description offers threads that can be pulled. I think this is especially relevant and important for public/external methods where a new reader might be starting a fresh read.

/// @dev Returns the deterministic address of the account created via `createAccount`.
/// @notice Returns the deterministic address of the account created via `createAccount()`.
///
/// @param owners The initial set of owners that was / will be provided to `createAccount()`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the tense is important to keep consistent. Most of this nat spec is written in the present tense

function initCodeHash() public view virtual returns (bytes32 result) {
result = LibClone.initCodeHashERC1967(implementation);
}

/// @dev Returns the salt that will be used for deterministic address
/// @notice Returns the salt that will be / was used to deploy the account (using create2).
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that tying these parameters to the createAccount() method is helpful. This method can be used for any arbitrary set of owners and nonce. I think the defs should be more generic here.

Suggested change
/// @notice Returns the salt that will be / was used to deploy the account (using create2).
/// @notice Returns the deterministic salt for a given `owner` set and `nonce`


/// @dev Validates the signature with ERC1271 return,
/// so that this account can also be used as a signer.
/// @return result `0x1626ba7e` if validation succeeded, else `0xffffffff`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Despite referencing EIP-1271 above, it might be clearer to define these "magic numbers" as constants above. If you opt to leave them as comments, then I think it's helpful to describe the derivation in the comment like:

Suggested change
/// @return result `0x1626ba7e` if validation succeeded, else `0xffffffff`.
/// @return result `0x1626ba7e` == bytes4(keccak256("isValidSignature(bytes32,bytes)") upon success, else `0xffffffff`

address public immutable implementation;

/// @notice Reverted when trying to create a new `CoinbaseSmartWallet` account without any owner.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I prefer the verb Thrown when describing errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tend to prefer 'reverts' as it is literally what we write (i.e., "revert MyError();") but open to debate. Maybe @wilsoncusack could share his view as well?

error InvalidOwnerBytesLength(bytes owner);

/// @notice Reverted when trying to intialize the contracts owners if a provided owner 32 bytes long
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// @notice Reverted when trying to intialize the contracts owners if a provided owner 32 bytes long
/// @notice Reverted when trying to intialize the contracts owners if a provided owner is 32 bytes long

event RemoveOwner(uint256 indexed index, bytes owner);

/// @notice Access control modifier ensuring the caller is an owner fo this account (or the account itself).
Copy link
Contributor

Choose a reason for hiding this comment

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

For two reasons, I think this should be simplified:

  1. It's virtual so the behavior might be changed at some point when overwritten
  2. The method _checkOwner()'s natspec should describe the cases where _checkOwner would pass or fail
Suggested change
/// @notice Access control modifier ensuring the caller is an owner fo this account (or the account itself).
/// @notice Access control modifier ensuring the caller is an authorized owner

function removeOwnerAtIndex(uint8 index) public virtual onlyOwner {
/// @notice Removes an owner from the given `index`.
///
/// @dev Revert if not owner is registered at `index`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this reads more clearly:

Suggested change
/// @dev Revert if not owner is registered at `index`.
/// @dev Reverts if the owner is not registered at `index`.

/// @notice Helper function to get a storage reference to the `MultiOwnableStorage` struct.
///
/// @return $ A storage reference to the `MultiOwnableStorage` struct.
function _$() internal pure returns (MultiOwnableStorage storage $) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Though definitely shorter, if we're claiming to follow ERC-7201 I think we should follow all of its conventions.

@xenoliss
Copy link
Contributor Author

xenoliss commented Mar 7, 2024

Closing as moved to #18.

@xenoliss xenoliss closed this Mar 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants