Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
// File: contracts/BaseRelayRecipient.sol
pragma solidity ^0.8.0;
abstract contract BaseRelayRecipient {
address public trustedForwarder;
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
return forwarder == trustedForwarder;
}
function versionRecipient() external view virtual returns (string memory) {
return "3.0.0-beta.10";
}
function _setTrustedForwarder(address _trustedForwarder) internal virtual;
function _msgSender() internal view virtual returns (address) {
if (isTrustedForwarder(msg.sender)) {
assembly {
return(add(calldataload(sub(calldatasize(), 20)), 12), 20)
}
} else {
return msg.sender;
}
}
function _msgData() internal view virtual returns (bytes calldata) {
if (isTrustedForwarder(msg.sender)) {
return msg.data[:msg.data.length - 20];
} else {
return msg.data;
}
}
}
// File: contracts/ECDSA.sol
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions are designed to operate on message hashes that are signed using the personal_sign
* method. To verify a message signed witheth_sign, calculate the hash of the message
* using {toEthSignedMessageHash}.
*
* See {recover}.
*/
library ECDSA {
/**
* @dev Recovers the address that signed a hash with a standard EIP-2098 signature.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the hash of the message signed
// v, r, s are components of the signature
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return ecrecover(hash, v, r, s);
} else if (signature.length == 64) {
bytes32 r;
bytes32 s;
// ecrecover takes the signature parameters, and the hash of the message signed
// r, s are components of the signature
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x00))
}
return ecrecover(hash, 27, r, s);
} else {
revert("ECDSA: invalid signature length");
}
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This produces hash corresponding
* to the one signed with the JSON-RPC method `eth_sign`.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:
32", hash));
}
}
// File: contracts/MegaBeastV3.sol
pragma solidity ^0.8.20;
/**
* @title MegaBeastV3 (Corrected)
* @notice A corrected and secured version of the arbitrage contract.
* @dev Fixes critical flaws in slippage protection and profit calculation.
*/
// Interfaces (condensed for brevity)
interface IERC20 {
function balanceOf(address owner) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
}
interface ILendingPool {
function flashLoan(address receiver, address[] calldata assets, uint256[] calldata amounts, uint256[] calldata modes, address onBehalfOf, bytes calldata params, uint16 referralCode) external;
}
interface IFlashLoanReceiver {
function executeOperation(address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, address initiator, bytes calldata params) external returns (bool);
}
// CORRECTED: SwapStep now includes the outputToken to accurately measure swap results.
struct SwapStep {
address router; // DEX router for this step
address outputToken; // The token this swap should produce
uint256 minAmountOut; // The minimum amount of outputToken to receive
bytes callData; // The swap calldata for the router
}
contract MegaBeastV3 is BaseRelayRecipient, IFlashLoanReceiver {
using ECDSA for bytes32;
address public owner;
ILendingPool public immutable LENDING_POOL;
address public profitToken;
address[] public sweepTokens;
mapping(uint256 => bool) public usedNonces;
uint256 public minProfitWei;
bool public paused;
uint256 private _status = 1;
// Modifiers
modifier onlyOwner() {
require(_msgSender() == owner, "Not owner");
_;
}
modifier nonReentrant() {
require(_status == 1, "Reentrant call");
_status = 2;
_;
_status = 1;
}
modifier whenNotPaused() {
require(!paused, "Paused");
_;
}
// Events
event SweptToken(address indexed token, uint256 amount, address indexed relayer, uint256 feePaid);
event ArbitrageExecuted(address indexed profitToken, uint256 profit);
event ArbitrageStarted(address[] assets, uint256[] amounts);
event Paused();
event Unpaused();
constructor(
address lendingPoolAddress,
address trustedForwarder,
address initialProfitToken,
uint256 _minProfitWei
) {
require(lendingPoolAddress != address(0) && trustedForwarder != address(0) && initialProfitToken != address(0), "Zero address");
owner = msg.sender;
LENDING_POOL = ILendingPool(lendingPoolAddress);
profitToken = initialProfitToken;
minProfitWei = _minProfitWei;
_setTrustedForwarder(trustedForwarder);
}
// --- FIXED: IMPLEMENTATION FOR BaseRelayRecipient ---
function _setTrustedForwarder(address _trustedForwarder) internal override {
trustedForwarder = _trustedForwarder;
}
/*** Flashloan Arbitrage ***/
function startArbitrage(
address[] calldata assetsToBorrow,
uint256[] calldata amountsToBorrow,
bytes calldata swapStepsEncoded
) external onlyOwner whenNotPaused {
require(assetsToBorrow.length > 0 && assetsToBorrow.length == amountsToBorrow.length, "Invalid input");
emit ArbitrageStarted(assetsToBorrow, amountsToBorrow);
uint256[] memory modes = new uint256[](assetsToBorrow.length);
LENDING_POOL.flashLoan(address(this), assetsToBorrow, amountsToBorrow, modes, address(this), swapStepsEncoded, 0);
}
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address,
bytes calldata params
) external override nonReentrant returns (bool) {
require(msg.sender == address(LENDING_POOL), "Unauthorized");
uint256 initialProfitTokenBalance = IERC20(profitToken).balanceOf(address(this));
SwapStep[] memory steps = abi.decode(params, (SwapStep[]));
for (uint i = 0; i < steps.length; i++) {
uint256 balanceBefore = IERC20(steps[i].outputToken).balanceOf(address(this));
(bool success, ) = steps[i].router.call(steps[i].callData);
require(success, "DEX swap failed");
uint256 balanceAfter = IERC20(steps[i].outputToken).balanceOf(address(this));
require(balanceAfter > balanceBefore, "No output from swap");
uint256 amountOut = balanceAfter - balanceBefore;
require(amountOut >= steps[i].minAmountOut, "Slippage exceeded");
}
for (uint i = 0; i < assets.length; i++) {
uint256 debt = amounts[i] + premiums[i];
require(IERC20(assets[i]).balanceOf(address(this)) >= debt, "Insufficient balance to repay");
IERC20(assets[i]).approve(address(LENDING_POOL), debt);
}
uint256 finalProfitTokenBalance = IERC20(profitToken).balanceOf(address(this));
if (finalProfitTokenBalance > initialProfitTokenBalance) {
uint256 profit = finalProfitTokenBalance - initialProfitTokenBalance;
require(profit >= minProfitWei, "Profit below threshold");
IERC20(profitToken).transfer(owner, profit);
emit ArbitrageExecuted(profitToken, profit);
} else {
require(finalProfitTokenBalance >= initialProfitTokenBalance, "Profit token loss");
}
return true;
}
/*** Gasless Sweeps ***/
function sweepTokensGasless(uint256 nonce, uint16 feeBps, bytes calldata ownerSignature) external whenNotPaused {
require(!usedNonces[nonce], "Nonce used");
require(feeBps <= 1000, "Fee too high");
bytes32 hash = keccak256(abi.encodePacked(nonce, feeBps, address(this)));
address signer = hash.toEthSignedMessageHash().recover(ownerSignature);
require(signer == owner, "Invalid signature");
usedNonces[nonce] = true;
for (uint i = 0; i < sweepTokens.length; i++) {
address token = sweepTokens[i];
uint256 balance = IERC20(token).balanceOf(address(this));
if (balance == 0) continue;
uint256 fee = (balance * feeBps) / 10000;
uint256 net = balance - fee;
if (fee > 0) IERC20(token).transfer(_msgSender(), fee);
if (net > 0) IERC20(token).transfer(owner, net);
emit SweptToken(token, balance, _msgSender(), fee);
}
}
/*** Owner Controls ***/
function setSweepTokens(address[] calldata tokens) external onlyOwner {
sweepTokens = tokens;
}
function setProfitToken(address newProfitToken) external onlyOwner {
require(newProfitToken != address(0), "Zero address");
profitToken = newProfitToken;
}
function setMinProfit(uint256 minWei) external onlyOwner {
minProfitWei = minWei;
}
function pause() external onlyOwner {
paused = true;
emit Paused();
}
function unpause() external onlyOwner {
paused = false;
emit Unpaused();
}
function withdraw(address token, uint256 amount) external onlyOwner {
uint256 bal = IERC20(token).balanceOf(address(this));
uint256 toSend = (amount == 0 || amount > bal) ? bal : amount;
if (toSend > 0) IERC20(token).transfer(owner, toSend);
}
function withdrawEther() external onlyOwner {
uint256 bal = address(this).balance;
if (bal > 0) payable(owner).transfer(bal);
}
receive() external payable {}
}
Submitted on: 2025-09-20 13:36:21
Comments
Log in to comment.
No comments yet.