function scheduleBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) external override onlyRole(PROPOSER_ROLE) returns (bytes32) {
if (targets.length == 0 || targets.length != values.length || targets.length != calldatas.length) {
revert InvalidTargetCount();
}
if (delay < _minDelay || delay > _maxDelay) {
revert InvalidDelay(delay);
}
if (predecessor != bytes32(0)) {
if (!isOperationDone(predecessor) && !isOperationPending(predecessor)) {
revert PredecessorNotExecuted(predecessor);
}
}
bytes32 id = hashOperationBatch(targets, values, calldatas, predecessor, salt);
if (_operations[id].timestamp != 0) revert OperationAlreadyScheduled(id);
uint256 timestamp = block.timestamp + delay;
_operations[id] = Operation({
timestamp: timestamp.toUint64(),
executed: false
});
emit OperationScheduled(id, targets, values, calldatas, predecessor, salt, delay);
return id;
}
function execute(uint256 proposalId) external override nonReentrant {
ProposalCore storage proposal = _proposals[proposalId];
if (proposal.executed) revert ProposalAlreadyExecuted(proposalId, block.timestamp);
ProposalState currentState = state(proposalId);
if (currentState == ProposalState.Succeeded) {
_queueProposal(proposalId);
} else if (currentState == ProposalState.Queued) {
_executeProposal(proposalId);
} else {
revert InvalidProposalState(
proposalId,
currentState,
currentState == ProposalState.Active ? ProposalState.Succeeded : ProposalState.Queued,
"Invalid state for execution"
);
}
}
Executing duplicate transactions can lead to unintended behavior.
I recommend implementing a check for each proposed action to verify that it is not already being executed within the same proposal.