-
Notifications
You must be signed in to change notification settings - Fork 369
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
Allow for CIP8 that bypasses CIP3 #8360
Changes from 12 commits
8f32170
75300aa
473c2a9
2fb497c
1239e86
c5cb7b7
fd1a949
09e7d44
124d276
faa42d7
768311f
ae17218
a48f60e
bd9f4d1
2bb9f98
cd53afb
f4dc8bf
7b0e2af
c1f72b8
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 |
---|---|---|
|
@@ -71,6 +71,9 @@ contract Accounts is | |
); | ||
bytes32 public eip712DomainSeparator; | ||
|
||
// A per-account list of CIP8 storage roots, bypassing CIP3. | ||
mapping(address => bytes[]) public offchainStorageRoots; | ||
|
||
bytes32 constant ValidatorSigner = keccak256(abi.encodePacked("celo.org/core/validator")); | ||
bytes32 constant AttestationSigner = keccak256(abi.encodePacked("celo.org/core/attestation")); | ||
bytes32 constant VoteSigner = keccak256(abi.encodePacked("celo.org/core/vote")); | ||
|
@@ -96,6 +99,8 @@ contract Accounts is | |
event AccountMetadataURLSet(address indexed account, string metadataURL); | ||
event AccountWalletAddressSet(address indexed account, address walletAddress); | ||
event AccountCreated(address indexed account); | ||
event OffchainStorageRootAdded(address indexed account, bytes url); | ||
event OffchainStorageRootRemoved(address indexed account, bytes url); | ||
|
||
/** | ||
* @notice Sets initialized == true on implementation contracts | ||
|
@@ -108,7 +113,7 @@ contract Accounts is | |
* @return The storage, major, minor, and patch version of the contract. | ||
*/ | ||
function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { | ||
return (1, 1, 2, 1); | ||
return (1, 1, 3, 0); | ||
} | ||
|
||
/** | ||
|
@@ -238,6 +243,66 @@ contract Accounts is | |
emit AccountMetadataURLSet(msg.sender, metadataURL); | ||
} | ||
|
||
/** | ||
* @notice Adds a new CIP8 storage root. | ||
* @param url The URL pointing to the offchain storage root. | ||
*/ | ||
function addStorageRoot(bytes calldata url) external { | ||
require(isAccount(msg.sender), "Unknown account"); | ||
offchainStorageRoots[msg.sender].push(url); | ||
emit OffchainStorageRootAdded(msg.sender, url); | ||
} | ||
|
||
/** | ||
* @notice Removes a CIP8 storage root. | ||
* @param index The index of the storage root to be removed in the account's | ||
* list of storage roots. | ||
* @dev The order of storage roots may change after this operation (the last | ||
* storage root will be moved to `index`), be aware of this if removing | ||
* multiple storage roots at a time. | ||
*/ | ||
function removeStorageRoot(uint256 index) external { | ||
require(isAccount(msg.sender), "Unknown account"); | ||
require(index < offchainStorageRoots[msg.sender].length); | ||
uint256 lastIndex = offchainStorageRoots[msg.sender].length - 1; | ||
bytes memory url = offchainStorageRoots[msg.sender][index]; | ||
offchainStorageRoots[msg.sender][index] = offchainStorageRoots[msg.sender][lastIndex]; | ||
offchainStorageRoots[msg.sender].length--; | ||
emit OffchainStorageRootRemoved(msg.sender, url); | ||
} | ||
|
||
/** | ||
* @notice Returns the full list of offchain storage roots for an account. | ||
* @param account The account whose storage roots to return. | ||
* @return List of storage root URLs. | ||
*/ | ||
function getOffchainStorageRoots(address account) | ||
external | ||
view | ||
returns (bytes memory, uint256[] memory) | ||
{ | ||
require(isAccount(account), "Unknown account"); | ||
uint256 numberRoots = offchainStorageRoots[account].length; | ||
uint256 totalLength = 0; | ||
for (uint256 i = 0; i < numberRoots; i++) { | ||
totalLength += offchainStorageRoots[account][i].length; | ||
} | ||
|
||
bytes memory concatenated = new bytes(totalLength); | ||
uint256 lastIndex = 0; | ||
uint256[] memory lengths = new uint256[](numberRoots); | ||
Comment on lines
+285
to
+293
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. does storing the roots concatenated make more sense? 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 believe that would make adding/removing roots more complicated? |
||
for (uint256 i = 0; i < numberRoots; i++) { | ||
bytes storage root = offchainStorageRoots[account][i]; | ||
lengths[i] = root.length; | ||
for (uint256 j = 0; j < lengths[i]; j++) { | ||
concatenated[lastIndex] = root[j]; | ||
lastIndex++; | ||
} | ||
} | ||
|
||
return (concatenated, lengths); | ||
} | ||
|
||
/** | ||
* @notice Set the indexed signer for a specific role | ||
* @param signer the address to set as default | ||
|
@@ -857,6 +922,56 @@ contract Accounts is | |
return (sizes, data); | ||
} | ||
|
||
/** | ||
* @notice Getter for the storage roots of multiple accounts. | ||
* @param accountsToQuery The addresses of the accounts to get the storage roots for. | ||
* @return ( | ||
* data - all strings concatenated | ||
* rootLengths[] - the length of each string in `data` in bytes | ||
* accountRootNumbers[] - the number of storage roots per account | ||
* ) | ||
*/ | ||
|
||
function batchGetOffchainStorageRoots(address[] calldata accountsToQuery) | ||
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. maybe lacking context but don't see why this is useful 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 believe avoiding round trips was the main goal with this. If this is supported out-of-contract that would be ideal (especially given how complicated this function ended up being), let's discuss this with the wallet team that would be consuming this. 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. agreed that I can't really envision a specific use case for this, we can always batch from the wallet side |
||
external | ||
view | ||
returns (bytes memory, uint256[] memory, uint256[] memory) | ||
{ | ||
uint256[] memory accountRootNumbers = new uint256[](accountsToQuery.length); | ||
uint256 totalRoots = 0; | ||
for (uint256 i = 0; i < accountsToQuery.length; i++) { | ||
require(isAccount(accountsToQuery[i]), "Unknown account"); | ||
accountRootNumbers[i] = offchainStorageRoots[accountsToQuery[i]].length; | ||
totalRoots += accountRootNumbers[i]; | ||
} | ||
|
||
uint256[] memory rootLengths = new uint256[](totalRoots); | ||
uint256 totalLength = 0; | ||
uint256 current = 0; | ||
|
||
for (uint256 i = 0; i < accountsToQuery.length; i++) { | ||
for (uint256 j = 0; j < accountRootNumbers[i]; j++) { | ||
rootLengths[current] = offchainStorageRoots[accountsToQuery[i]][j].length; | ||
totalLength += rootLengths[current]; | ||
current++; | ||
} | ||
} | ||
|
||
bytes memory concatenated = new bytes(totalLength); | ||
current = 0; | ||
for (uint256 i = 0; i < accountsToQuery.length; i++) { | ||
for (uint256 j = 0; j < accountRootNumbers[i]; j++) { | ||
bytes storage root = offchainStorageRoots[accountsToQuery[i]][j]; | ||
for (uint256 k = 0; k < root.length; k++) { | ||
concatenated[current] = root[k]; | ||
current++; | ||
} | ||
} | ||
} | ||
|
||
return (concatenated, rootLengths, accountRootNumbers); | ||
} | ||
|
||
/** | ||
* @notice Getter for the data encryption key and version. | ||
* @param account The address of the account to get the key for | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -205,6 +205,11 @@ rule viewFunctionsDoNotRevert(method f) filtered { f -> | |
// some functions we ignore, and the reasons: | ||
&& f.selector != hasAuthorizedSigner(address,string).selector // Calldatasize may not match | ||
&& f.selector != batchGetMetadataURL(address[]).selector // Calldatasize may not match | ||
|
||
// These require an account to exist | ||
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. 🙏 |
||
&& f.selector != getOffchainStorageRoots(address).selector | ||
&& f.selector != batchGetOffchainStorageRoots(address[]).selector | ||
&& f.selector != offchainStorageRoots(address,uint256).selector | ||
} { | ||
env e; | ||
require e.msg.value == 0; // view functions are not payable | ||
|
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 index should also be emitted here for cache invalidation