Platform: Code4rena
Start Date: 12/07/2023
Pot Size: $80,000 USDC
Total HM: 11
Participants: 47
Period: 9 days
Judge: berndartmueller
Total Solo HM: 1
Id: 260
League: ETH
Rank: 34/47
Findings: 1
Award: $43.33
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: immeas
Also found by: Bauchibred, DavidGiladi, Emmanuel, Jeiwan, MohammedRizwan, MrPotatoMagic, Rolezn, Sathish9098, T1MOH, Udsen, banpaleo5, hals, matrix_0wl, naman1778
43.3267 USDC - $43.33
ID | Title | Severity |
---|---|---|
[L-01] | Lack of minimum signers & minimum threshold check | Low |
[L-02] | Signers could lose their balance of tokens if the target function uses tx.origin | Low |
[L-03] | Executed interchain proposals can be re-executed again | Low |
[L-04] | Interchain proposals can be executed out of order | Low |
[NC-01] | hasSignerVoted function returns false if the proposal has been executed | Non Critical |
[NC-02] | getSignerVotesCount function returns zero if the proposal has been executed | Non Critical |
_rotateSigners
function design; it requires at least two signers to be set; so at least one vote is only required to execute a proposal or change signers (minimu threshold).threshold
and a minimum of 3 signers.Mitigate the risk of executing malicious proposals/changing signers if one of the two signers accounts is compromised.
File:contracts/cgp/auth/MultisigBase.sol Line 157: length = newAccounts.length;
Manual Testing.
In _rotateSigners
function: ensure that the minimum signers number is >= 3,and minimum threshold is >= 2.
tx.origin
AxelarServiceGovernance.sol
/executeMultisigProposal
function & in Multisig.sol
/execute
function:call
opcode.tx.origin
.Signers could lose their tokens balance.
File:contracts/cgp/util/Caller.sol Line 18: (bool success, ) = target.call{ value: nativeValue }(callData);
File:contracts/cgp/governance/Multisig.sol Line 30-36: function execute( address target, bytes calldata callData, uint256 nativeValue ) external payable onlySigners { _call(target, callData, nativeValue); }
File:contracts/cgp/governance/AxelarServiceGovernance.sol Line 48-62: function executeMultisigProposal( address target, bytes calldata callData, uint256 nativeValue ) external payable onlySigners { bytes32 proposalHash = keccak256(abi.encodePacked(target, callData, nativeValue)); if (!multisigApprovals[proposalHash]) revert NotApproved(); multisigApprovals[proposalHash] = false; _call(target, callData, nativeValue); emit MultisigExecuted(proposalHash, target, callData, nativeValue); }
Manual Testing.
Signers should be advised to use an untouched wallet address so that target code interaction can't harm them.
In InterchainProposalExecutor
contract/_execute
function : there's no check if the proposal that's going to be executed by the relayers has been executed before or not.
This will lead to funds drainage if the proposal has been executed before if the proposal has a value to send to the target.
File:contracts/interchain-governance-executor/InterchainProposalExecutor.sol Line 41-67: function _execute( string calldata sourceChain, string calldata sourceAddress, bytes calldata payload ) internal override { _beforeProposalExecuted(sourceChain, sourceAddress, payload); // Check that the source address is whitelisted if (!whitelistedSenders[sourceChain][StringToAddress.toAddress(sourceAddress)]) { revert NotWhitelistedSourceAddress(); } // Decode the payload (address interchainProposalCaller, InterchainCalls.Call[] memory calls) = abi.decode(payload, (address, InterchainCalls.Call[])); // Check that the caller is whitelisted if (!whitelistedCallers[sourceChain][interchainProposalCaller]) { revert NotWhitelistedCaller(); } // Execute the proposal with the given arguments _executeProposal(calls); _onProposalExecuted(sourceChain, sourceAddress, interchainProposalCaller, payload); emit ProposalExecuted(keccak256(abi.encode(sourceChain, sourceAddress, interchainProposalCaller, payload))); }
Manual Testing.
Add a mechanism to track the proposals; so that the relayer can check if the proposal has been executed before or not.
Interchain proposals can be executed out of order; any relayer can call the _execute
function to execute the proposals in any order they want.
A malicious relayer can execute the proposals in a way that is beneficial to them.
File:contracts/interchain-governance-executor/InterchainProposalExecutor.sol Line 41-67: function _execute( string calldata sourceChain, string calldata sourceAddress, bytes calldata payload ) internal override { _beforeProposalExecuted(sourceChain, sourceAddress, payload); // Check that the source address is whitelisted if (!whitelistedSenders[sourceChain][StringToAddress.toAddress(sourceAddress)]) { revert NotWhitelistedSourceAddress(); } // Decode the payload (address interchainProposalCaller, InterchainCalls.Call[] memory calls) = abi.decode(payload, (address, InterchainCalls.Call[])); // Check that the caller is whitelisted if (!whitelistedCallers[sourceChain][interchainProposalCaller]) { revert NotWhitelistedCaller(); } // Execute the proposal with the given arguments _executeProposal(calls); _onProposalExecuted(sourceChain, sourceAddress, interchainProposalCaller, payload); emit ProposalExecuted(keccak256(abi.encode(sourceChain, sourceAddress, interchainProposalCaller, payload))); }
Manual Testing.
Check that proposals are always executed in order; otherwise, if the risk is deemed acceptable, update the documentation to highlight that.
hasSignerVoted
function returns false if the proposal has been executedIf the proposal has been executed then it will return false for all signers; it's only valid if the proposal hasn't been executed yet.
File:contracts/cgp/auth/MultisigBase.sol Line 111-113: function hasSignerVoted(address account, bytes32 topic) external view override returns (bool) { return votingPerTopic[signerEpoch][topic].hasVoted[account]; }
Manual Testing.
Add another mechanism to save the votes of the signers even after the proposal execution.
getSignerVotesCount
function returns zero if the proposal has been executedgetSignerVotesCount
function is by design for the current running proposals voting, so if the proposal has been executed then it will return zero.
File:contracts/cgp/auth/MultisigBase.sol Line 119-129: function getSignerVotesCount(bytes32 topic) external view override returns (uint256) { uint256 length = signers.accounts.length; uint256 voteCount; for (uint256 i; i < length; ++i) { if (votingPerTopic[signerEpoch][topic].hasVoted[signers.accounts[i]]) { voteCount++; } } return voteCount; }
Manual Testing.
Add another mechanism to save the number of votes of a proposal even after the proposal execution.
#0 - c4-pre-sort
2023-07-29T01:06:35Z
0xSorryNotSorry marked the issue as high quality report
#1 - c4-judge
2023-09-08T11:31:43Z
berndartmueller marked the issue as grade-b