-
Notifications
You must be signed in to change notification settings - Fork 36
feat(ServiceGovernance): external Multisig #128
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
Merged
Merged
Changes from 37 commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
db1ac92
feat(InterchainMultisig): weighted multisig contract
re1ro 4e173c5
refactor(InterchainMultisig): isLatestSigners
re1ro 383228d
fix(InterchainMultisig): improving domain separation
re1ro 2efd070
refactor(InterchainMultisig): using executeCalls as a single entrypoint
re1ro b1f44df
Update contracts/governance/BaseWeightedMultisig.sol
re1ro 7a0230c
Update contracts/governance/BaseWeightedMultisig.sol
re1ro 706b44c
fix(InterchainMultisig): addressed PR feedback
re1ro d8fccab
feat(BaseWeightedMultisig): configurable OLD_SIGNERS_RETENTION
re1ro 3f2f65d
Update contracts/governance/BaseWeightedMultisig.sol
re1ro 686d107
Update contracts/governance/BaseWeightedMultisig.sol
re1ro ddf496a
Update contracts/governance/BaseWeightedMultisig.sol
re1ro ed21583
Update contracts/governance/InterchainMultisig.sol
re1ro 9ac8d57
Update contracts/governance/BaseWeightedMultisig.sol
re1ro c0bd935
Update contracts/governance/InterchainMultisig.sol
re1ro 46684f3
Update contracts/interfaces/IInterchainMultisig.sol
re1ro acc5faf
Update contracts/governance/InterchainMultisig.sol
re1ro ece2b52
Update contracts/interfaces/IInterchainMultisig.sol
re1ro 67e6391
fix(InterchainMultisig): addressed PR feedback
re1ro 611910e
test(BaseWeightedMultisig): coverage
re1ro 028d753
refactor(BaseWeightedMultisig): using legacy proof encoding
re1ro 31432be
Update contracts/governance/BaseWeightedMultisig.sol
re1ro 1646631
Update contracts/governance/BaseWeightedMultisig.sol
re1ro dcc2cec
Update contracts/governance/BaseWeightedMultisig.sol
re1ro a898efa
Update contracts/governance/BaseWeightedMultisig.sol
re1ro 296a486
Update contracts/interfaces/IInterchainMultisig.sol
re1ro d82ef70
Update contracts/governance/InterchainMultisig.sol
re1ro 85f713d
Update contracts/governance/BaseWeightedMultisig.sol
re1ro c89adbc
Update contracts/governance/BaseWeightedMultisig.sol
re1ro e8ed7b5
Update contracts/governance/InterchainMultisig.sol
re1ro d1dfbe7
Update contracts/interfaces/IBaseWeightedMultisig.sol
re1ro 97c5825
refactor(BaseWeightedMultisig): PR feedback
re1ro 6e561bb
test: fixing bytecode checks
re1ro 07c410c
fix(InterchainMultisig): slither warnings
re1ro 8d2dfc2
test: fixing bytecode checks
re1ro 1e380f4
feat(ServiceGovernance): external Multisig
re1ro c0bf164
test(InterchainMultisig): coverage
re1ro b87b9fb
test(InterchainMultisig): coverage
re1ro f7f693c
test(InterchainMultisig): more coverage
re1ro eddadaa
test(InterchainMultisig): more coverage
re1ro 9624502
test(InterchainMultisig): PR feedback
re1ro 971ce23
feat(ServiceGovernance): mutisig transfer
re1ro 44fba61
feat(ServiceGovernance): mutisig transfer event
re1ro 62bd23d
feat(InterchainMultisig): batch ID
re1ro 1491b5b
fix(InterchainMultisig): executedCalls count
re1ro afe1cb5
fix(InterchainMultisig): executedCalls count
re1ro 6d4fa74
Update test/governance/BaseWeightedMultisig.js
re1ro 85a39e6
Update test/governance/BaseWeightedMultisig.js
re1ro 162dd06
fix(InterchainMultisig): improved tests
re1ro a3ba57c
Update contracts/governance/AxelarServiceGovernance.sol
re1ro 77ee3e3
Merge branch 'feat/multisig-executor' into feat/service-governance-mu…
re1ro c4ff672
fix(AxelarServiceGovernance): improved tests
re1ro 6f11f2e
fix(InterchainMultisig): addressed audit finding
re1ro a3cdc3a
fix(InterchainMultisig): using noop for voiding batch id
re1ro f35835e
Update contracts/governance/BaseWeightedMultisig.sol
re1ro 637a047
Update contracts/governance/InterchainMultisig.sol
re1ro 6f1a3bb
refactor(BaseWeightedMultisig): PR feedback
re1ro 01bc50f
refactor(BaseWeightedMultisig): PR feedback
re1ro 930a324
Update contracts/governance/InterchainMultisig.sol
re1ro b526e8a
test(InterchainMultisig): full coverage
re1ro 188024a
doc(BaseWeightedMultisig): proof example
re1ro 970475b
Merge branch 'feat/multisig-executor' into feat/service-governance-mu…
re1ro df8b5fb
Update contracts/governance/BaseWeightedMultisig.sol
re1ro d5146a4
refactor(InterchainMultisig): storage slot
re1ro 2986243
revert(Caller): bytecode affecting changes
re1ro c23f679
test(InterchainMultisig): live network compatibility
re1ro 8e0a3f3
test(InterchainMultisig): full coverage
re1ro b95756f
Merge branch 'feat/multisig-executor' into feat/service-governance-mu…
re1ro 79d938f
test(AxelarServiceGovernance): livenet tests optimization
re1ro 49e7ee4
Merge branch 'main' into feat/service-governance-multisig
re1ro 873b8a3
test(BaseWeightedMultisig): avoid hardcoded epoch values
re1ro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.0; | ||
|
|
||
| import { IBaseWeightedMultisig } from '../interfaces/IBaseWeightedMultisig.sol'; | ||
| import { ECDSA } from '../libs/ECDSA.sol'; | ||
|
|
||
| abstract contract BaseWeightedMultisig is IBaseWeightedMultisig { | ||
| // keccak256('WeightedMultisig.Storage'); | ||
| bytes32 private constant BASE_WEIGHTED_STORAGE_LOCATION = | ||
| 0xa233fbcae4dcfad00091a9d8ff9561f12b3db9ec7227470684b4617d40a38746; | ||
|
|
||
| struct WeightedSigners { | ||
| address[] signers; | ||
| uint256[] weights; | ||
| uint256 threshold; | ||
| } | ||
|
|
||
| struct WeightedMultisigStorage { | ||
| uint256 epoch; | ||
| mapping(uint256 => bytes32) signerHashByEpoch; | ||
| mapping(bytes32 => uint256) epochBySignerHash; | ||
| } | ||
|
|
||
| // @dev Previous signers retention. 0 means only the current signers are valid | ||
| // @return The number of epochs to keep the signers valid for signature verification | ||
| uint256 public immutable previousSignersRetention; | ||
|
|
||
| // @param previousSignersRetentionEpochs The number of epochs to keep previous signers valid for signature verification | ||
| constructor(uint256 previousSignersRetentionEpochs) { | ||
| previousSignersRetention = previousSignersRetentionEpochs; | ||
| } | ||
|
|
||
| /**********************\ | ||
| |* External Functions *| | ||
| \**********************/ | ||
|
|
||
| /* | ||
| * @notice This function returns the current signers epoch | ||
| * @return uint256 The current signers epoch | ||
| */ | ||
| function epoch() external view returns (uint256) { | ||
| return _baseWeightedStorage().epoch; | ||
| } | ||
|
|
||
| /* | ||
| * @notice This function returns the signers hash for a given epoch | ||
| * @param signerEpoch The given epoch | ||
| * @return bytes32 The signers hash for the given epoch | ||
| */ | ||
| function signerHashByEpoch(uint256 signerEpoch) external view returns (bytes32) { | ||
| return _baseWeightedStorage().signerHashByEpoch[signerEpoch]; | ||
| } | ||
|
|
||
| /* | ||
| * @notice This function returns the epoch for a given signers hash | ||
| * @param signerHash The signers hash | ||
| * @return uint256 The epoch for the given signers hash | ||
| */ | ||
| function epochBySignerHash(bytes32 signerHash) external view returns (uint256) { | ||
| return _baseWeightedStorage().epochBySignerHash[signerHash]; | ||
| } | ||
|
|
||
| /* | ||
| * @notice This function takes messageHash and proof data and reverts if proof is invalid | ||
| * @param messageHash The hash of the message that was signed | ||
| * @param weightedSigners The weighted signers data | ||
| * @param signatures The signatures data | ||
| * @return isLatestSigners True if provided signers are the current ones | ||
| */ | ||
| function validateProof(bytes32 messageHash, bytes calldata proof) public view returns (bool isLatestSigners) { | ||
| if (proof.length < 32 * 4) revert InvalidProof(); | ||
|
|
||
| WeightedMultisigStorage storage slot = _baseWeightedStorage(); | ||
| // slither-disable-next-line uninitialized-local | ||
| WeightedSigners memory weightedSet; | ||
| bytes[] memory signatures; | ||
|
|
||
| (weightedSet.signers, weightedSet.weights, weightedSet.threshold, signatures) = abi.decode( | ||
| proof, | ||
| (address[], uint256[], uint256, bytes[]) | ||
| ); | ||
|
|
||
| bytes32 signersHash = keccak256(abi.encode(weightedSet.signers, weightedSet.weights, weightedSet.threshold)); | ||
| uint256 signerEpoch = slot.epochBySignerHash[signersHash]; | ||
| uint256 currentEpoch = slot.epoch; | ||
|
|
||
| isLatestSigners = signerEpoch == currentEpoch; | ||
|
|
||
| if (signatures.length == 0) revert MalformedSignatures(); | ||
| if (signerEpoch == 0 || currentEpoch - signerEpoch > previousSignersRetention) revert InvalidSigners(); | ||
|
|
||
| _validateSignatures(messageHash, weightedSet, signatures); | ||
| } | ||
|
|
||
| /*************************\ | ||
| |* Integration Functions *| | ||
| \*************************/ | ||
|
|
||
| /* | ||
| * @notice This function rotates the current signers with a new set of signers | ||
| * @param newWeightedSigners The new weighted signers data | ||
| */ | ||
| function _rotateSigners(WeightedSigners memory newSigners) internal { | ||
| WeightedMultisigStorage storage slot = _baseWeightedStorage(); | ||
|
|
||
| uint256 length = newSigners.signers.length; | ||
| uint256 totalWeight; | ||
|
|
||
| // signers must be sorted binary or alphabetically in lower case | ||
| if (length == 0 || !_isSortedAscAndContainsNoDuplicate(newSigners.signers)) revert InvalidSigners(); | ||
|
|
||
| if (newSigners.weights.length != length) revert InvalidWeights(); | ||
|
|
||
| for (uint256 i; i < length; ++i) { | ||
| uint256 weight = newSigners.weights[i]; | ||
|
|
||
| if (weight == 0) revert InvalidWeights(); | ||
|
|
||
| totalWeight = totalWeight + weight; | ||
| } | ||
|
|
||
| if (newSigners.threshold == 0 || totalWeight < newSigners.threshold) revert InvalidThreshold(); | ||
|
|
||
| bytes32 newSignersHash = keccak256(abi.encode(newSigners.signers, newSigners.weights, newSigners.threshold)); | ||
|
|
||
| uint256 newEpoch = slot.epoch + 1; | ||
| // slither-disable-next-line costly-loop | ||
| slot.epoch = newEpoch; | ||
| slot.signerHashByEpoch[newEpoch] = newSignersHash; | ||
| // if signer set is the same, old epoch will be overwritten | ||
| slot.epochBySignerHash[newSignersHash] = newEpoch; | ||
|
|
||
| emit SignersRotated(newSigners.signers, newSigners.weights, newSigners.threshold); | ||
| } | ||
|
|
||
| /**********************\ | ||
| |* Internal Functions *| | ||
| \**********************/ | ||
|
|
||
| /* | ||
| * @notice This function takes messageHash and proof data and reverts if proof is invalid | ||
| * @param messageHash The hash of the message that was signed | ||
| * @param weighted The weighted signers data | ||
| * @param signatures The sorted signatures data | ||
| */ | ||
| function _validateSignatures( | ||
| bytes32 messageHash, | ||
| WeightedSigners memory weightedSigners, | ||
| bytes[] memory signatures | ||
| ) internal pure { | ||
| uint256 signersLength = weightedSigners.signers.length; | ||
| uint256 signaturesLength = signatures.length; | ||
| uint256 signerIndex; | ||
| uint256 totalWeight; | ||
|
|
||
| // looking for signers within signers | ||
| // this requires both signers and signatures to be sorted | ||
| // having it sorted allows us to avoid the full inner loop to find a match | ||
| for (uint256 i; i < signaturesLength; ++i) { | ||
| address signer = ECDSA.recover(messageHash, signatures[i]); | ||
|
|
||
| // looping through remaining signers to find a match | ||
| for (; signerIndex < signersLength && signer != weightedSigners.signers[signerIndex]; ++signerIndex) {} | ||
|
|
||
| // checking if we are out of signers | ||
| if (signerIndex == signersLength) revert MalformedSignatures(); | ||
|
|
||
| // accumulating signatures weight | ||
| totalWeight = totalWeight + weightedSigners.weights[signerIndex]; | ||
|
|
||
| // weight needs to reach or surpass threshold | ||
| if (totalWeight >= weightedSigners.threshold) return; | ||
|
|
||
| // increasing signers index if match was found | ||
| ++signerIndex; | ||
| } | ||
| // if weight sum below threshold | ||
| revert LowSignaturesWeight(); | ||
| } | ||
|
|
||
| /* | ||
| * @notice This function checks if the provided signers are sorted and contain no duplicates | ||
| * @param signers The signers to check | ||
| * @return True if the signers are sorted and contain no duplicates | ||
| */ | ||
| function _isSortedAscAndContainsNoDuplicate(address[] memory signers) internal pure returns (bool) { | ||
| uint256 signersLength = signers.length; | ||
| address prevSigner = signers[0]; | ||
|
|
||
| if (prevSigner == address(0)) return false; | ||
|
|
||
| for (uint256 i = 1; i < signersLength; ++i) { | ||
| address currSigner = signers[i]; | ||
|
|
||
| if (prevSigner >= currSigner) { | ||
| return false; | ||
| } | ||
|
|
||
| prevSigner = currSigner; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /* | ||
| * @notice Gets the storage slot for the WeightedMultisigStorage struct | ||
| * @return the storage slot | ||
| */ | ||
| function _baseWeightedStorage() private pure returns (WeightedMultisigStorage storage slot) { | ||
| assembly { | ||
| slot.slot := BASE_WEIGHTED_STORAGE_LOCATION | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.