Skip to content
Merged
Show file tree
Hide file tree
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 Dec 19, 2023
4e173c5
refactor(InterchainMultisig): isLatestSigners
re1ro Dec 19, 2023
383228d
fix(InterchainMultisig): improving domain separation
re1ro Jan 4, 2024
2efd070
refactor(InterchainMultisig): using executeCalls as a single entrypoint
re1ro Jan 11, 2024
b1f44df
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Jan 22, 2024
7a0230c
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Jan 22, 2024
706b44c
fix(InterchainMultisig): addressed PR feedback
re1ro Jan 23, 2024
d8fccab
feat(BaseWeightedMultisig): configurable OLD_SIGNERS_RETENTION
re1ro Jan 24, 2024
3f2f65d
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Jan 25, 2024
686d107
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Jan 25, 2024
ddf496a
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Jan 25, 2024
ed21583
Update contracts/governance/InterchainMultisig.sol
re1ro Jan 25, 2024
9ac8d57
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Jan 25, 2024
c0bd935
Update contracts/governance/InterchainMultisig.sol
re1ro Jan 25, 2024
46684f3
Update contracts/interfaces/IInterchainMultisig.sol
re1ro Jan 25, 2024
acc5faf
Update contracts/governance/InterchainMultisig.sol
re1ro Jan 25, 2024
ece2b52
Update contracts/interfaces/IInterchainMultisig.sol
re1ro Jan 25, 2024
67e6391
fix(InterchainMultisig): addressed PR feedback
re1ro Jan 26, 2024
611910e
test(BaseWeightedMultisig): coverage
re1ro Jan 30, 2024
028d753
refactor(BaseWeightedMultisig): using legacy proof encoding
re1ro Feb 1, 2024
31432be
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Feb 1, 2024
1646631
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Feb 1, 2024
dcc2cec
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Feb 1, 2024
a898efa
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Feb 1, 2024
296a486
Update contracts/interfaces/IInterchainMultisig.sol
re1ro Feb 1, 2024
d82ef70
Update contracts/governance/InterchainMultisig.sol
re1ro Feb 1, 2024
85f713d
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Feb 1, 2024
c89adbc
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Feb 1, 2024
e8ed7b5
Update contracts/governance/InterchainMultisig.sol
re1ro Feb 1, 2024
d1dfbe7
Update contracts/interfaces/IBaseWeightedMultisig.sol
re1ro Feb 1, 2024
97c5825
refactor(BaseWeightedMultisig): PR feedback
re1ro Feb 6, 2024
6e561bb
test: fixing bytecode checks
re1ro Feb 6, 2024
07c410c
fix(InterchainMultisig): slither warnings
re1ro Feb 6, 2024
8d2dfc2
test: fixing bytecode checks
re1ro Feb 6, 2024
1e380f4
feat(ServiceGovernance): external Multisig
re1ro Feb 7, 2024
c0bf164
test(InterchainMultisig): coverage
re1ro Feb 8, 2024
b87b9fb
test(InterchainMultisig): coverage
re1ro Feb 8, 2024
f7f693c
test(InterchainMultisig): more coverage
re1ro Feb 8, 2024
eddadaa
test(InterchainMultisig): more coverage
re1ro Feb 8, 2024
9624502
test(InterchainMultisig): PR feedback
re1ro Feb 12, 2024
971ce23
feat(ServiceGovernance): mutisig transfer
re1ro Feb 12, 2024
44fba61
feat(ServiceGovernance): mutisig transfer event
re1ro Feb 12, 2024
62bd23d
feat(InterchainMultisig): batch ID
re1ro Feb 12, 2024
1491b5b
fix(InterchainMultisig): executedCalls count
re1ro Feb 12, 2024
afe1cb5
fix(InterchainMultisig): executedCalls count
re1ro Feb 12, 2024
6d4fa74
Update test/governance/BaseWeightedMultisig.js
re1ro Feb 15, 2024
85a39e6
Update test/governance/BaseWeightedMultisig.js
re1ro Feb 15, 2024
162dd06
fix(InterchainMultisig): improved tests
re1ro Feb 15, 2024
a3ba57c
Update contracts/governance/AxelarServiceGovernance.sol
re1ro Feb 15, 2024
77ee3e3
Merge branch 'feat/multisig-executor' into feat/service-governance-mu…
re1ro Feb 15, 2024
c4ff672
fix(AxelarServiceGovernance): improved tests
re1ro Feb 15, 2024
6f11f2e
fix(InterchainMultisig): addressed audit finding
re1ro Feb 20, 2024
a3cdc3a
fix(InterchainMultisig): using noop for voiding batch id
re1ro Feb 20, 2024
f35835e
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Feb 21, 2024
637a047
Update contracts/governance/InterchainMultisig.sol
re1ro Feb 21, 2024
6f1a3bb
refactor(BaseWeightedMultisig): PR feedback
re1ro Feb 21, 2024
01bc50f
refactor(BaseWeightedMultisig): PR feedback
re1ro Feb 21, 2024
930a324
Update contracts/governance/InterchainMultisig.sol
re1ro Feb 21, 2024
b526e8a
test(InterchainMultisig): full coverage
re1ro Feb 22, 2024
188024a
doc(BaseWeightedMultisig): proof example
re1ro Feb 22, 2024
970475b
Merge branch 'feat/multisig-executor' into feat/service-governance-mu…
re1ro Feb 22, 2024
df8b5fb
Update contracts/governance/BaseWeightedMultisig.sol
re1ro Feb 23, 2024
d5146a4
refactor(InterchainMultisig): storage slot
re1ro Feb 24, 2024
2986243
revert(Caller): bytecode affecting changes
re1ro Feb 24, 2024
c23f679
test(InterchainMultisig): live network compatibility
re1ro Feb 24, 2024
8e0a3f3
test(InterchainMultisig): full coverage
re1ro Feb 24, 2024
b95756f
Merge branch 'feat/multisig-executor' into feat/service-governance-mu…
re1ro Feb 24, 2024
79d938f
test(AxelarServiceGovernance): livenet tests optimization
re1ro Feb 26, 2024
49e7ee4
Merge branch 'main' into feat/service-governance-multisig
re1ro Feb 26, 2024
873b8a3
test(BaseWeightedMultisig): avoid hardcoded epoch values
re1ro Feb 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions contracts/governance/AxelarServiceGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,41 @@ import { BaseMultisig } from './BaseMultisig.sol';
* @dev This contract is part of the Axelar Governance system, it inherits the Interchain Governance contract
* with added functionality to approve and execute multisig proposals.
*/
contract AxelarServiceGovernance is InterchainGovernance, BaseMultisig, IAxelarServiceGovernance {
contract AxelarServiceGovernance is InterchainGovernance, IAxelarServiceGovernance {
enum ServiceGovernanceCommand {
ScheduleTimeLockProposal,
CancelTimeLockProposal,
ApproveMultisigProposal,
CancelMultisigApproval
}

address public multisig;

mapping(bytes32 => bool) public multisigApprovals;

modifier onlyMultisig() {
if (msg.sender != multisig) revert NotAuthorized();
_;
}

/**
* @notice Initializes the contract.
* @param gateway_ The address of the Axelar gateway contract
* @param governanceChain_ The name of the governance chain
* @param governanceAddress_ The address of the governance contract
* @param minimumTimeDelay The minimum time delay for timelock operations
* @param cosigners The list of initial signers
* @param threshold The number of required signers to validate a transaction
* @param multisig_ The list of initial signers
*/
constructor(
address gateway_,
string memory governanceChain_,
string memory governanceAddress_,
uint256 minimumTimeDelay,
address[] memory cosigners,
uint256 threshold
)
InterchainGovernance(gateway_, governanceChain_, governanceAddress_, minimumTimeDelay)
BaseMultisig(cosigners, threshold)
{}
address multisig_
) InterchainGovernance(gateway_, governanceChain_, governanceAddress_, minimumTimeDelay) {
if (multisig_ == address(0)) revert InvalidMultisigAddress();
multisig = multisig_;
}

/**
* @notice Returns whether a multisig proposal has been approved
Expand All @@ -67,7 +72,7 @@ contract AxelarServiceGovernance is InterchainGovernance, BaseMultisig, IAxelarS
address target,
bytes calldata callData,
uint256 nativeValue
) external payable onlySigners {
) external payable onlyMultisig {
bytes32 proposalHash = _getProposalHash(target, callData, nativeValue);

if (!multisigApprovals[proposalHash]) revert NotApproved();
Expand All @@ -79,6 +84,14 @@ contract AxelarServiceGovernance is InterchainGovernance, BaseMultisig, IAxelarS
_call(target, callData, nativeValue);
}

function transferMultisig(address newMultisig) external onlyMultisig {
if (newMultisig == address(0)) revert InvalidMultisigAddress();

emit MultisigTransferred(multisig, newMultisig);

multisig = newMultisig;
}

/**
* @notice Internal function to process a governance command
* @param commandType The type of the command
Expand Down Expand Up @@ -109,18 +122,6 @@ contract AxelarServiceGovernance is InterchainGovernance, BaseMultisig, IAxelarS
} else if (commandType == uint256(ServiceGovernanceCommand.ApproveMultisigProposal)) {
multisigApprovals[proposalHash] = true;

// Reset all previous votes for this proposal
_resetSignerVotes(
keccak256(
abi.encodeWithSelector(
AxelarServiceGovernance.executeMultisigProposal.selector,
target,
callData,
nativeValue
)
)
);

emit MultisigApproved(proposalHash, target, callData, nativeValue);
return;
} else if (commandType == uint256(ServiceGovernanceCommand.CancelMultisigApproval)) {
Expand Down
215 changes: 215 additions & 0 deletions contracts/governance/BaseWeightedMultisig.sol
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
}
}
}
Loading