-
Notifications
You must be signed in to change notification settings - Fork 102
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
Do not allow removeOwnerAtIndex to remove last owner, add function for removing last owner #43
Changes from all commits
a98bbd2
144321d
683e1b8
14e1258
6eb0175
abeb4b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,68 @@ | ||
AddOwnerAddressTest:testEmitsAddOwner() (gas: 92393) | ||
AddOwnerAddressTest:testIncreasesOwnerIndex() (gas: 90633) | ||
AddOwnerAddressTest:testRevertsIfAlreadyOwner() (gas: 93175) | ||
AddOwnerAddressTest:testEmitsAddOwner() (gas: 92417) | ||
AddOwnerAddressTest:testIncreasesOwnerIndex() (gas: 90691) | ||
AddOwnerAddressTest:testRevertsIfAlreadyOwner() (gas: 93210) | ||
AddOwnerAddressTest:testRevertsIfCalledByNonOwner() (gas: 11764) | ||
AddOwnerAddressTest:testSetsIsOwner() (gas: 90495) | ||
AddOwnerAddressTest:testSetsOwnerAtIndex() (gas: 98916) | ||
AddOwnerPublicKeyTest:testEmitsAddOwner() (gas: 115528) | ||
AddOwnerPublicKeyTest:testFuzzIsOwnerPublicKey(bytes32,bytes32) (runs: 256, μ: 115352, ~: 115352) | ||
AddOwnerPublicKeyTest:testRevertsIfAlreadyOwner() (gas: 116339) | ||
AddOwnerAddressTest:testSetsIsOwner() (gas: 90485) | ||
AddOwnerAddressTest:testSetsOwnerAtIndex() (gas: 98985) | ||
AddOwnerPublicKeyTest:testEmitsAddOwner() (gas: 115552) | ||
AddOwnerPublicKeyTest:testFuzzIsOwnerPublicKey(bytes32,bytes32) (runs: 256, μ: 115343, ~: 115343) | ||
AddOwnerPublicKeyTest:testRevertsIfAlreadyOwner() (gas: 116374) | ||
AddOwnerPublicKeyTest:testRevertsIfCalledByNonOwner() (gas: 11754) | ||
AddOwnerPublicKeyTest:testSetsIsOwner() (gas: 113794) | ||
AddOwnerPublicKeyTest:testSetsOwnerAtIndex() (gas: 128562) | ||
CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForDeployedAccount() (gas: 308670) | ||
CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForUndeployedAccount() (gas: 291699) | ||
CoinbaseSmartWalletFactoryTest:testDeployDeterministicPassValues() (gas: 267742) | ||
CoinbaseSmartWalletFactoryTest:test_CreateAccount_ReturnsPredeterminedAddress_WhenAccountAlreadyExists() (gas: 287406) | ||
CoinbaseSmartWalletFactoryTest:test_createAccountDeploysToPredeterminedAddress() (gas: 269093) | ||
CoinbaseSmartWalletFactoryTest:test_createAccountSetsOwnersCorrectly() (gas: 277981) | ||
CoinbaseSmartWalletFactoryTest:test_implementation_returnsExpectedAddress() (gas: 7676) | ||
AddOwnerPublicKeyTest:testSetsIsOwner() (gas: 113784) | ||
AddOwnerPublicKeyTest:testSetsOwnerAtIndex() (gas: 128631) | ||
CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForDeployedAccount() (gas: 308549) | ||
CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForUndeployedAccount() (gas: 291578) | ||
CoinbaseSmartWalletFactoryTest:testDeployDeterministicPassValues() (gas: 267610) | ||
CoinbaseSmartWalletFactoryTest:test_CreateAccount_ReturnsPredeterminedAddress_WhenAccountAlreadyExists() (gas: 287363) | ||
CoinbaseSmartWalletFactoryTest:test_RevertsIfLength32ButLargerThanAddress() (gas: 318999) | ||
CoinbaseSmartWalletFactoryTest:test_createAccountDeploysToPredeterminedAddress() (gas: 268961) | ||
CoinbaseSmartWalletFactoryTest:test_createAccountSetsOwnersCorrectly() (gas: 277827) | ||
CoinbaseSmartWalletFactoryTest:test_exitIfAccountIsAlreadyInitialized() (gas: 268078) | ||
CoinbaseSmartWalletFactoryTest:test_implementation_returnsExpectedAddress() (gas: 7654) | ||
CoinbaseSmartWalletFactoryTest:test_initCodeHash() (gas: 7890) | ||
CoinbaseSmartWalletFactoryTest:test_revertsIfNoOwners() (gas: 29214) | ||
ERC1271Test:test_returnsExpectedDomainHashWhenProxy() (gas: 15384) | ||
ERC1271Test:test_static() (gas: 3057560) | ||
MultiOwnableInitializeTest:testRevertsIfLength32ButLargerThanAddress() (gas: 78927) | ||
MultiOwnableInitializeTest:testRevertsIfLength32NotAddress() (gas: 78908) | ||
MultiOwnableInitializeTest:testRevertsIfLengthNot32Or64() (gas: 101211) | ||
RemoveOwnerAtIndexTest:testEmitsRemoveOwner() (gas: 33270) | ||
RemoveOwnerAtIndexTest:testRemovesOwner() (gas: 32653) | ||
RemoveOwnerAtIndexTest:testRemovesOwnerAtIndex() (gas: 27538) | ||
RemoveOwnerAtIndexTest:testRevertsIfCalledByNonOwner() (gas: 11308) | ||
RemoveOwnerAtIndexTest:testRevertsIfNoOwnerAtIndex() (gas: 16624) | ||
TestCanSkipChainIdValidation:test_approvedSelectorsReturnTrue() (gas: 15933) | ||
TestCanSkipChainIdValidation:test_otherSelectorsReturnFalse() (gas: 12513) | ||
TestExecuteWithoutChainIdValidation:testExecute() (gas: 424907) | ||
TestExecuteWithoutChainIdValidation:testExecuteBatch() (gas: 729143) | ||
TestExecuteWithoutChainIdValidation:testExecuteBatch(uint256) (runs: 256, μ: 3450733, ~: 3582947) | ||
TestExecuteWithoutChainIdValidation:test__codesize() (gas: 49053) | ||
TestExecuteWithoutChainIdValidation:test__codesize() (gas: 49288) | ||
TestExecuteWithoutChainIdValidation:test_canChangeOwnerWithoutChainId() (gas: 287727) | ||
TestExecuteWithoutChainIdValidation:test_cannotCallExec() (gas: 220025) | ||
ERC1271Test:test_returnsExpectedDomainHashWhenProxy() (gas: 29176) | ||
ERC1271Test:test_static() (gas: 3180535) | ||
MultiOwnableInitializeTest:testRevertsIfLength32ButLargerThanAddress() (gas: 78972) | ||
MultiOwnableInitializeTest:testRevertsIfLength32NotAddress() (gas: 78953) | ||
MultiOwnableInitializeTest:testRevertsIfLengthNot32Or64() (gas: 101256) | ||
RemoveLastOwnerTest:test_emitsRemoveOwner() (gas: 50337) | ||
RemoveLastOwnerTest:test_removesOwner() (gas: 49609) | ||
RemoveLastOwnerTest:test_removesOwnerAtIndex() (gas: 49562) | ||
RemoveLastOwnerTest:test_revert_whenCalledByNonOwner(address) (runs: 256, μ: 19094, ~: 19094) | ||
RemoveLastOwnerTest:test_revert_whenNoOwnerAtIndex() (gas: 48379) | ||
RemoveLastOwnerTest:test_revert_whenWrongOwnerAtIndex() (gas: 34153) | ||
RemoveLastOwnerTest:test_reverts_whenNotLastOwner() (gas: 123427) | ||
RemoveOwnerAtIndexTest:test_emitsRemoveOwner() (gas: 55524) | ||
RemoveOwnerAtIndexTest:test_removesOwner() (gas: 54784) | ||
RemoveOwnerAtIndexTest:test_removesOwnerAtIndex() (gas: 54540) | ||
RemoveOwnerAtIndexTest:test_revert_whenCalledByNonOwner(address) (runs: 256, μ: 19145, ~: 19145) | ||
RemoveOwnerAtIndexTest:test_revert_whenNoOwnerAtIndex() (gas: 33372) | ||
RemoveOwnerAtIndexTest:test_revert_whenWrongOwnerAtIndex() (gas: 36694) | ||
RemoveOwnerAtIndexTest:test_reverts_ifIsLastOwner() (gas: 7470450) | ||
TestCanSkipChainIdValidation:test_approvedSelectorsReturnTrue() (gas: 15757) | ||
TestCanSkipChainIdValidation:test_otherSelectorsReturnFalse() (gas: 12425) | ||
TestExecuteWithoutChainIdValidation:testExecute() (gas: 424786) | ||
TestExecuteWithoutChainIdValidation:testExecuteBatch() (gas: 729088) | ||
TestExecuteWithoutChainIdValidation:testExecuteBatch(uint256) (runs: 256, μ: 3529376, ~: 3409593) | ||
TestExecuteWithoutChainIdValidation:test__codesize() (gas: 49665) | ||
TestExecuteWithoutChainIdValidation:test__codesize() (gas: 49900) | ||
TestExecuteWithoutChainIdValidation:test_canChangeOwnerWithoutChainId() (gas: 287838) | ||
TestExecuteWithoutChainIdValidation:test_cannotCallExec() (gas: 12758) | ||
TestExecuteWithoutChainIdValidation:test_revertsIfCallerNotEntryPoint() (gas: 8598) | ||
TestExecuteWithoutChainIdValidation:test_revertsIfWrongNonceKey() (gas: 62275) | ||
TestExecuteWithoutChainIdValidation:test_revertsWithReservedNonce() (gas: 82324) | ||
TestInitialize:testInitialize() (gas: 21034) | ||
TestInitialize:test_cannotInitImplementation() (gas: 2714468) | ||
TestExecuteWithoutChainIdValidation:test_revertsIfWrongNonceKey() (gas: 62298) | ||
TestExecuteWithoutChainIdValidation:test_revertsWithReservedNonce() (gas: 82370) | ||
TestImplementation:testImplementation() (gas: 12601) | ||
TestInitialize:testInitialize() (gas: 21100) | ||
TestInitialize:test_cannotInitImplementation() (gas: 2836084) | ||
TestIsValidSignature:testReturnsInvalidIfPasskeySigButWrongOwnerLength() (gas: 39469) | ||
TestIsValidSignature:testRevertsIfEthereumSignatureButWrongOwnerLength() (gas: 24040) | ||
TestIsValidSignature:testSmartWalletSigner() (gas: 2990421) | ||
TestIsValidSignature:testRevertsIfOwnerIsInvalidEthereumAddress() (gas: 22021) | ||
TestIsValidSignature:testSmartWalletSigner() (gas: 3111896) | ||
TestIsValidSignature:testValidateSignatureWithEOASigner() (gas: 24922) | ||
TestIsValidSignature:testValidateSignatureWithEOASignerFailsWithWrongSigner() (gas: 23877) | ||
TestIsValidSignature:testValidateSignatureWithPasskeySigner() (gas: 421256) | ||
TestIsValidSignature:testValidateSignatureWithPasskeySignerFailsBadOwnerIndex() (gas: 34958) | ||
TestIsValidSignature:testValidateSignatureWithPasskeySignerFailsWithWrongBadSignature() (gas: 428722) | ||
TestUpgradeToAndCall:testUpgradeToAndCall() (gas: 25477) | ||
TestValidateUserOp:testValidateUserOp() (gas: 447275) | ||
TestUpgradeToAndCall:testUpgradeToAndCall() (gas: 25522) | ||
TestValidateUserOp:testValidateUserOp() (gas: 447366) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,8 @@ pragma solidity ^0.8.4; | |
struct MultiOwnableStorage { | ||
/// @dev Tracks the index of the next owner to add. | ||
uint256 nextOwnerIndex; | ||
/// @dev Tracks number of owners that have been removed. | ||
uint256 removedOwnersCount; | ||
/// @dev Mapping of indices to raw owner bytes, used to idenfitied owners by their | ||
/// uint256 id. | ||
/// | ||
|
@@ -52,8 +54,9 @@ contract MultiOwnable { | |
/// @notice Thrown when `owner` argument does not match owner found at index. | ||
/// | ||
/// @param index The index of the owner to be removed. | ||
/// @param owner Owner to be removed which did not match owner found at index. | ||
error WrongOwnerAtIndex(uint256 index, bytes owner); | ||
/// @param expectedOwner The owner passed in the remove call. | ||
/// @param actualOwner The actual owner at `index`. | ||
error WrongOwnerAtIndex(uint256 index, bytes expectedOwner, bytes actualOwner); | ||
|
||
/// @notice Thrown when trying to intialize the contracts owners if a provided owner is neither | ||
/// 64 bytes long (for passkey) nor a valid address. | ||
|
@@ -67,6 +70,14 @@ contract MultiOwnable { | |
/// @param owner The invalid raw abi encoded owner bytes. | ||
error InvalidEthereumAddressOwner(bytes owner); | ||
|
||
/// @notice Thrown when removeOwnerAtIndex is called and there is only one current owner. | ||
error LastOwner(); | ||
|
||
/// @notice Thrown when removeLastOwner is called and there is more than one current owner. | ||
/// | ||
/// @param ownersRemaining The number of current owners. | ||
error NotLastOwner(uint256 ownersRemaining); | ||
|
||
/// @notice Emitted when a new owner is registered. | ||
/// | ||
/// @param index The owner index. | ||
|
@@ -88,33 +99,51 @@ contract MultiOwnable { | |
/// @notice Convenience function to add a new owner address. | ||
/// | ||
/// @param owner The owner address. | ||
function addOwnerAddress(address owner) public virtual onlyOwner { | ||
function addOwnerAddress(address owner) external virtual onlyOwner { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. other clean up I am grouping in here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if this is overly restrictive. I could imagine a plugin/module that might help users manage owners. Depending on how the community agrees on module/plugin support, this call could be executed in the context of this contract. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume we would do an upgrade to enable this? So we could change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair point There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wt now? DM me pls |
||
_addOwner(abi.encode(owner)); | ||
} | ||
|
||
/// @notice Convenience function to add a new owner passkey. | ||
/// | ||
/// @param x The owner public key x coordinate. | ||
/// @param y The owner public key y coordinate. | ||
function addOwnerPublicKey(bytes32 x, bytes32 y) public virtual onlyOwner { | ||
function addOwnerPublicKey(bytes32 x, bytes32 y) external virtual onlyOwner { | ||
_addOwner(abi.encode(x, y)); | ||
} | ||
|
||
/// @notice Removes an owner from the given `index`. | ||
/// @notice Removes owner at the given `index`. | ||
/// | ||
/// @dev Reverts if the provided owner is not found at `index`. | ||
/// @dev Reverts if the owner is not registered at `index`. | ||
/// @dev Reverts if there is currently only one owner. | ||
/// @dev Reverts if `owner` does not match bytes found at `index`. | ||
/// | ||
/// @param index The index to remove from. | ||
/// @param owner_ The owner at the specific index. | ||
function removeOwnerAtIndex(uint256 index, bytes calldata owner_) public virtual onlyOwner { | ||
bytes memory owner = ownerAtIndex(index); | ||
if (owner.length == 0) revert NoOwnerAtIndex(index); | ||
if (keccak256(owner) != keccak256(owner_)) revert WrongOwnerAtIndex(index, owner_); | ||
/// @param index The index of the owner to be removed. | ||
/// @param owner The ABI encoded bytes of the owner to be removed. | ||
function removeOwnerAtIndex(uint256 index, bytes calldata owner) external virtual onlyOwner { | ||
MultiOwnableStorage storage $ = _getMultiOwnableStorage(); | ||
if ($.nextOwnerIndex - $.removedOwnersCount == 1) { | ||
revert LastOwner(); | ||
} | ||
|
||
_removeOwnerAtIndex(index, owner); | ||
} | ||
|
||
delete _getMultiOwnableStorage().isOwner[owner]; | ||
delete _getMultiOwnableStorage().ownerAtIndex[index]; | ||
/// @notice Removes owner at the given `index`, which should be the only current owner. | ||
/// | ||
/// @dev Reverts if the owner is not registered at `index`. | ||
/// @dev Reverts if there is currently more than one owner. | ||
/// @dev Reverts if `owner` does not match bytes found at `index`. | ||
/// | ||
/// @param index The index of the owner to be removed. | ||
/// @param owner The ABI encoded bytes of the owner to be removed. | ||
function removeLastOwner(uint256 index, bytes calldata owner) external virtual onlyOwner { | ||
MultiOwnableStorage storage $ = _getMultiOwnableStorage(); | ||
uint256 ownersRemaining = $.nextOwnerIndex - $.removedOwnersCount; | ||
if (ownersRemaining > 1) { | ||
revert NotLastOwner(ownersRemaining); | ||
} | ||
|
||
emit RemoveOwner(index, owner); | ||
_removeOwnerAtIndex(index, owner); | ||
} | ||
|
||
/// @notice Checks if the given `account` address is registered as owner. | ||
|
@@ -161,6 +190,13 @@ contract MultiOwnable { | |
return _getMultiOwnableStorage().nextOwnerIndex; | ||
} | ||
|
||
/// @notice Tracks the number of owners removed, used with nextOwnerIndex to avoid removing all owners | ||
/// | ||
/// @return The number of owners that have been removed. | ||
function removedOwnersCount() public view virtual returns (uint256) { | ||
return _getMultiOwnableStorage().removedOwnersCount; | ||
} | ||
|
||
/// @notice Initialize the owners of this contract. | ||
/// | ||
/// @dev Intended to be called when the smart account is first deployed. | ||
|
@@ -197,12 +233,35 @@ contract MultiOwnable { | |
function _addOwnerAtIndex(bytes memory owner, uint256 index) internal virtual { | ||
if (isOwnerBytes(owner)) revert AlreadyOwner(owner); | ||
|
||
_getMultiOwnableStorage().isOwner[owner] = true; | ||
_getMultiOwnableStorage().ownerAtIndex[index] = owner; | ||
MultiOwnableStorage storage $ = _getMultiOwnableStorage(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wt ru guys doing |
||
$.isOwner[owner] = true; | ||
$.ownerAtIndex[index] = owner; | ||
|
||
emit AddOwner(index, owner); | ||
} | ||
|
||
/// @notice Removes owner at the given `index`. | ||
/// | ||
/// @dev Reverts if the owner is not registered at `index`. | ||
/// @dev Reverts if `owner` does not match bytes found at `index`. | ||
/// | ||
/// @param index The index of the owner to be removed. | ||
/// @param owner The ABI encoded bytes of the owner to be removed. | ||
function _removeOwnerAtIndex(uint256 index, bytes calldata owner) internal virtual { | ||
bytes memory owner_ = ownerAtIndex(index); | ||
if (owner_.length == 0) revert NoOwnerAtIndex(index); | ||
if (keccak256(owner_) != keccak256(owner)) { | ||
revert WrongOwnerAtIndex({index: index, expectedOwner: owner, actualOwner: owner_}); | ||
} | ||
|
||
MultiOwnableStorage storage $ = _getMultiOwnableStorage(); | ||
delete $.isOwner[owner]; | ||
delete $.ownerAtIndex[index]; | ||
$.removedOwnersCount++; | ||
|
||
emit RemoveOwner(index, owner); | ||
} | ||
|
||
/// @notice Checks if the sender is an owner of this contract or the contract itself. | ||
/// | ||
/// @dev Revert if the sender is not an owner fo the contract itself. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.23; | ||
|
||
import "./RemoveOwnerBase.t.sol"; | ||
|
||
contract RemoveLastOwnerTest is RemoveOwnerBaseTest { | ||
function setUp() public override { | ||
owners.push(owner1Bytes); | ||
mock.init(owners); | ||
index = 0; | ||
ownerToRemove = owner1Bytes; | ||
} | ||
|
||
function test_reverts_whenNotLastOwner() public { | ||
vm.prank(owner1Address); | ||
mock.addOwnerPublicKey("x", "y"); | ||
vm.expectRevert(abi.encodeWithSelector(MultiOwnable.NotLastOwner.selector, 2)); | ||
_removeOwner(); | ||
} | ||
|
||
function _removeOwner() internal override { | ||
vm.prank(owner1Address); | ||
mock.removeLastOwner(index, ownerToRemove); | ||
} | ||
} |
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.
@stevieraykatz this felt nice to have to me
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.
Agreed -- could help track down malicious replays