-
Notifications
You must be signed in to change notification settings - Fork 53
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
Conversation
/// @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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this auto formatted?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
/// @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 |
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @param owners The initial set of owners that was / will be provided to `createAccount()`. | |
/// @param owners The initial set of owners provided to `createAccount()`. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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: |
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 $) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this idiomatic?
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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()`. |
There was a problem hiding this comment.
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). |
There was a problem hiding this comment.
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.
/// @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`. |
There was a problem hiding this comment.
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:
/// @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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @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). |
There was a problem hiding this comment.
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:
- It's virtual so the behavior might be changed at some point when overwritten
- The method
_checkOwner()
's natspec should describe the cases where _checkOwner would pass or fail
/// @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`. |
There was a problem hiding this comment.
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:
/// @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 $) { |
There was a problem hiding this comment.
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.
Closing as moved to #18. |
This PR generally improves NatSpec doc on the contracts defined in
src/
.