223 lines
7.4 KiB
Solidity
223 lines
7.4 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
|
|
|
/**
|
|
* @title PacaBotManager
|
|
* @dev A multicall contract that acts as a botOnly abstraction for PACA contracts
|
|
* @notice This contract allows the owner to execute multiple bot-only functions across different PACA contracts
|
|
*/
|
|
contract PacaBotManager is Ownable, ReentrancyGuard {
|
|
|
|
// Events
|
|
event CallExecuted(address indexed target, bytes4 indexed selector, bool success);
|
|
event MultiCallExecuted(uint256 callCount, uint256 successCount);
|
|
|
|
// Errors
|
|
error CallFailed(uint256 callIndex, address target, bytes data, bytes returnData);
|
|
error EmptyCallData();
|
|
error InvalidTarget();
|
|
|
|
struct Call {
|
|
address target;
|
|
bytes callData;
|
|
}
|
|
|
|
struct CallResult {
|
|
bool success;
|
|
bytes returnData;
|
|
}
|
|
|
|
constructor() Ownable(msg.sender) {
|
|
// Deployer is automatically the owner
|
|
}
|
|
|
|
/**
|
|
* @notice Execute a single call to a target contract
|
|
* @param target The contract address to call
|
|
* @param callData The encoded function call data
|
|
* @return success Whether the call succeeded
|
|
* @return returnData The return data from the call
|
|
*/
|
|
function executeCall(
|
|
address target,
|
|
bytes calldata callData
|
|
)
|
|
external
|
|
onlyOwner
|
|
nonReentrant
|
|
returns (bool success, bytes memory returnData)
|
|
{
|
|
if (target == address(0)) revert InvalidTarget();
|
|
if (callData.length == 0) revert EmptyCallData();
|
|
|
|
(success, returnData) = target.call(callData);
|
|
|
|
// Emit event (selector extraction simplified for testing)
|
|
emit CallExecuted(target, bytes4(0), success);
|
|
|
|
return (success, returnData);
|
|
}
|
|
|
|
/**
|
|
* @notice Execute multiple calls atomically - all must succeed or all revert
|
|
* @param calls Array of Call structs containing target and callData
|
|
* @return results Array of CallResult structs with success status and return data
|
|
*/
|
|
function multiCallAtomic(Call[] calldata calls)
|
|
external
|
|
onlyOwner
|
|
nonReentrant
|
|
returns (CallResult[] memory results)
|
|
{
|
|
uint256 length = calls.length;
|
|
results = new CallResult[](length);
|
|
|
|
for (uint256 i = 0; i < length; i++) {
|
|
if (calls[i].target == address(0)) revert InvalidTarget();
|
|
if (calls[i].callData.length == 0) revert EmptyCallData();
|
|
|
|
(bool success, bytes memory returnData) = calls[i].target.call(calls[i].callData);
|
|
|
|
if (!success) {
|
|
revert CallFailed(i, calls[i].target, calls[i].callData, returnData);
|
|
}
|
|
|
|
results[i] = CallResult({
|
|
success: success,
|
|
returnData: returnData
|
|
});
|
|
|
|
// Emit event (selector extraction simplified for testing)
|
|
emit CallExecuted(calls[i].target, bytes4(0), success);
|
|
}
|
|
|
|
emit MultiCallExecuted(length, length);
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* @notice Execute multiple calls with partial success allowed
|
|
* @param calls Array of Call structs containing target and callData
|
|
* @return results Array of CallResult structs with success status and return data
|
|
*/
|
|
function multiCallPartial(Call[] calldata calls)
|
|
external
|
|
onlyOwner
|
|
nonReentrant
|
|
returns (CallResult[] memory results)
|
|
{
|
|
uint256 length = calls.length;
|
|
results = new CallResult[](length);
|
|
uint256 successCount = 0;
|
|
|
|
for (uint256 i = 0; i < length; i++) {
|
|
if (calls[i].target == address(0) || calls[i].callData.length == 0) {
|
|
results[i] = CallResult({
|
|
success: false,
|
|
returnData: abi.encode("Invalid target or empty call data")
|
|
});
|
|
continue;
|
|
}
|
|
|
|
(bool success, bytes memory returnData) = calls[i].target.call(calls[i].callData);
|
|
|
|
results[i] = CallResult({
|
|
success: success,
|
|
returnData: returnData
|
|
});
|
|
|
|
if (success) {
|
|
successCount++;
|
|
}
|
|
|
|
// Emit event (selector extraction simplified for testing)
|
|
emit CallExecuted(calls[i].target, bytes4(0), success);
|
|
}
|
|
|
|
emit MultiCallExecuted(length, successCount);
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* @notice Convenience function to clear stakes for a user on a specific PACA contract
|
|
* @param pacaContract The PACA contract address
|
|
* @param user The user whose stakes will be cleared
|
|
*/
|
|
function clearStakes(address pacaContract, address user)
|
|
external
|
|
onlyOwner
|
|
nonReentrant
|
|
{
|
|
bytes memory callData = abi.encodeWithSignature("clearStakes(address)", user);
|
|
(bool success, bytes memory returnData) = pacaContract.call(callData);
|
|
|
|
if (!success) {
|
|
revert CallFailed(0, pacaContract, callData, returnData);
|
|
}
|
|
|
|
emit CallExecuted(pacaContract, bytes4(0), success);
|
|
}
|
|
|
|
/**
|
|
* @notice Convenience function to clear vestings for a user on a specific PACA contract
|
|
* @param pacaContract The PACA contract address
|
|
* @param user The user whose vestings will be cleared
|
|
*/
|
|
function clearVesting(address pacaContract, address user)
|
|
external
|
|
onlyOwner
|
|
nonReentrant
|
|
{
|
|
bytes memory callData = abi.encodeWithSignature("clearVesting(address)", user);
|
|
(bool success, bytes memory returnData) = pacaContract.call(callData);
|
|
|
|
if (!success) {
|
|
revert CallFailed(0, pacaContract, callData, returnData);
|
|
}
|
|
|
|
emit CallExecuted(pacaContract, bytes4(0), success);
|
|
}
|
|
|
|
/**
|
|
* @notice Convenience function to clear withdraw stakes for a user on a specific PACA contract
|
|
* @param pacaContract The PACA contract address
|
|
* @param user The user whose withdraw stakes will be cleared
|
|
*/
|
|
function clearWithdrawStakes(address pacaContract, address user)
|
|
external
|
|
onlyOwner
|
|
nonReentrant
|
|
{
|
|
bytes memory callData = abi.encodeWithSignature("clearWithdrawStakes(address)", user);
|
|
(bool success, bytes memory returnData) = pacaContract.call(callData);
|
|
|
|
if (!success) {
|
|
revert CallFailed(0, pacaContract, callData, returnData);
|
|
}
|
|
|
|
emit CallExecuted(pacaContract, bytes4(0), success);
|
|
}
|
|
|
|
/**
|
|
* @notice Emergency function to recover any accidentally sent ETH
|
|
*/
|
|
function emergencyWithdrawETH() external onlyOwner {
|
|
payable(owner()).transfer(address(this).balance);
|
|
}
|
|
|
|
/**
|
|
* @notice Emergency function to recover any accidentally sent ERC20 tokens
|
|
* @param token The token contract address
|
|
* @param amount The amount to recover
|
|
*/
|
|
function emergencyWithdrawToken(address token, uint256 amount) external onlyOwner {
|
|
// Simple transfer call - assumes standard ERC20
|
|
(bool success,) = token.call(
|
|
abi.encodeWithSignature("transfer(address,uint256)", owner(), amount)
|
|
);
|
|
require(success, "Token transfer failed");
|
|
}
|
|
} |