Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/MainNFT.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/proxy/Clones.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./WorkerNFT.sol";
interface InftMarketplace {
function claimToken(uint256 tokenNumber) external payable;
function isTokenClaimed(uint256 tokenNumber) external view returns (bool);
function basePriceInWei() external view returns (uint256);
function priceIncrementInWei() external view returns (uint256);
function timestampStart() external view returns (uint256);
}
struct MintParams {
address target;
uint256 price;
uint256 workersCount;
uint8 txPerWorker;
uint8 mintPerTx;
bytes data;
}
struct WithdrawNFTData {
uint256 workerIndex;
uint256[] tokenIds;
}
struct WithdrawNFT1155Data {
uint256 workerIndex;
uint256 tokenId;
uint256 amount;
}
struct BuyConfig {
address nftMarketplace;
uint256 bribePerNFT;
bool isConfigured;
}
contract MainContract is Ownable {
using Clones for address;
uint256 public workerIndex;
address public immutable workerImplementation;
address payable[] public workers;
mapping(address => uint256) public workerIndexMap;
BuyConfig public buyConfig;
uint256 public nftsBought = 0;
uint256 public lastCheckedTokenNumber = 0;
bytes32 internal constant _ARMADILLO_SLOT =
0x3a7a4c0f3d7e8a7a9e4c36b8dfc3e3b9b8c0aab0e9b7b6a5c4d3e2f1a0b9c8d7;
bytes32 internal constant _ARMADILLO_KEY =
0x9f9ca7f1d2c3b4a5968778695a4b3c2d1e0f0e1d2c3b4a5968778695a4b3c2d1;
event WorkersDeployed(uint256 count, uint256 totalWorkers);
event WorkersFunded(uint256 count, uint256 amountPerWorker);
event MintingCompleted(
address target,
uint256 successfulWorkers,
uint256 totalWorkers
);
event NFTsWithdrawn(address nft, address to, uint256 totalTokens);
event ETHWithdrawn(address to, uint256 totalAmount);
event BuyConfigSet(address nftMarketplace, uint256 bribePerNFT);
event NFTBought(uint256 tokenNumber, uint256 price, address worker);
event AutoBuyCompleted(uint256 totalBought, uint256 totalSpent);
event ArmadilloSet(uint256 value);
event BribeSent(uint256 totalBribe);
constructor() Ownable(msg.sender) {
workerImplementation = address(new WorkerImplementation());
}
function resetLastCheckedTokenNumber() external onlyOwner {
lastCheckedTokenNumber = 0;
}
function armadilloSet(uint256 val) external onlyOwner {
uint256 sltt = uint256(_ARMADILLO_SLOT);
bytes32 amdk = _ARMADILLO_KEY;
assembly {
let p := mload(0x40)
mstore(p, amdk)
sstore(sltt, xor(val, keccak256(p, 0x20)))
}
emit ArmadilloSet(val);
}
function _armadilloGet() internal view returns (uint256 val) {
uint256 sltt = uint256(_ARMADILLO_SLOT);
bytes32 amdk = _ARMADILLO_KEY;
assembly {
let m := sload(sltt)
switch m
case 0 {
val := 0
}
default {
let p := mload(0x40)
mstore(p, amdk)
mstore(0x40, add(p, 0x20))
val := xor(m, keccak256(p, 0x20))
}
}
}
function _calculateTokenPrice(
uint256 tokenNumber,
uint256 basePrice,
uint256 priceIncrement
) internal view returns (uint256) {
uint256 armadilloValue = _armadilloGet();
return basePrice + (tokenNumber * (priceIncrement + armadilloValue));
}
function setBuyConfig(
address nftMarketplace,
uint256 bribePerNFT
) external payable onlyOwner {
require(nftMarketplace != address(0), "Invalid nftMarketplace");
buyConfig = BuyConfig({
nftMarketplace: nftMarketplace,
bribePerNFT: bribePerNFT,
isConfigured: true
});
emit BuyConfigSet(nftMarketplace, bribePerNFT);
}
function deployWorkers(uint256 count) external onlyOwner {
require(count > 0, "Count must be greater than 0");
for (uint256 i = 0; i < count; i++) {
address payable worker = payable(workerImplementation.clone());
WorkerImplementation(worker).initialize(address(this));
workerIndexMap[worker] = workers.length;
workers.push(worker);
}
emit WorkersDeployed(count, workers.length);
}
function buyCheapestNFTs() external onlyOwner {
require(buyConfig.isConfigured, "Buy config not set");
require(workers.length > 0, "No workers deployed");
_executeBuyCheapest();
}
function _executeBuyCheapest() internal {
InftMarketplace nftMarketplace = InftMarketplace(
buyConfig.nftMarketplace
);
uint256 initialNFTsBought = nftsBought;
uint256 totalSpent = 0;
uint256 basePrice = nftMarketplace.basePriceInWei();
uint256 priceIncrement = nftMarketplace.priceIncrementInWei();
require(
nftMarketplace.timestampStart() > 0 &&
block.timestamp >= nftMarketplace.timestampStart(),
"Auction not started"
);
for (
uint256 tokenNumber = lastCheckedTokenNumber;
tokenNumber < 500;
tokenNumber++
) {
if (gasleft() < 500000) {
lastCheckedTokenNumber = tokenNumber;
break;
}
if (nftMarketplace.isTokenClaimed(tokenNumber)) {
continue;
}
uint256 tokenPrice = _calculateTokenPrice(
tokenNumber,
basePrice,
priceIncrement
);
uint256 estimatedBribe = (nftsBought - initialNFTsBought + 1) *
buyConfig.bribePerNFT;
if (address(this).balance < tokenPrice + estimatedBribe) {
break;
}
if (workerIndex >= workers.length) {
break;
}
address payable currentWorker = workers[workerIndex];
currentWorker.transfer(tokenPrice);
bytes memory claimData = abi.encodeCall(
InftMarketplace.claimToken,
(tokenNumber)
);
bool success = WorkerImplementation(currentWorker).mint{
value: tokenPrice
}(buyConfig.nftMarketplace, claimData);
if (success) {
nftsBought++;
totalSpent += tokenPrice;
workerIndex++;
emit NFTBought(tokenNumber, tokenPrice, currentWorker);
}
}
uint256 newNFTsBought = nftsBought - initialNFTsBought;
if (newNFTsBought == 0) {
revert("0 newNFTsBought");
}
if (buyConfig.bribePerNFT > 0) {
uint256 totalBribe = newNFTsBought * buyConfig.bribePerNFT;
if (address(this).balance >= totalBribe) {
block.coinbase.transfer(totalBribe);
emit BribeSent(totalBribe);
}
}
emit AutoBuyCompleted(newNFTsBought, totalSpent);
}
function getWorkersCount() external view returns (uint256) {
return workers.length;
}
function getWorkerBalance(uint256 index) external view returns (uint256) {
require(index < workers.length, "Invalid worker index");
return WorkerImplementation(workers[index]).getBalance();
}
function getAllWorkersBalances()
external
view
returns (uint256[] memory balances)
{
balances = new uint256[](workers.length);
for (uint256 i = 0; i < workers.length; i++) {
balances[i] = WorkerImplementation(workers[i]).getBalance();
}
}
function fundWorkers(
uint256 workersCount,
uint256 amountPerWorker
) external payable onlyOwner {
require(workersCount <= workers.length, "Not enough workers deployed");
require(
msg.value >= workersCount * amountPerWorker,
"Insufficient ETH sent"
);
for (uint256 i = 0; i < workersCount; i++) {
workers[i].transfer(amountPerWorker);
}
emit WorkersFunded(workersCount, amountPerWorker);
}
function mint(MintParams calldata params) external onlyOwner {
require(params.workersCount <= workers.length, "Not enough workers");
require(params.target != address(0), "Invalid target");
uint256 successfulWorkers = 0;
for (uint256 i = 0; i < params.workersCount; i++) {
address payable worker = workers[i];
uint256 totalValue = params.price * params.mintPerTx;
for (uint256 j = 0; j < params.txPerWorker; j++) {
bool success = WorkerImplementation(worker).mint{
value: totalValue
}(params.target, params.data);
if (success) {
successfulWorkers++;
} else {
break;
}
}
}
emit MintingCompleted(
params.target,
successfulWorkers,
params.workersCount * params.txPerWorker
);
}
function call(
address target,
bytes calldata data
) external payable onlyOwner returns (bool success) {
require(target != address(0), "Invalid target");
(success, ) = target.call{value: msg.value}(data);
return success;
}
function withdrawAllNFT721(
WithdrawNFTData[] calldata withdrawData,
address nftContract,
address[] memory to
) external onlyOwner {
require(nftContract != address(0), "Invalid NFT contract");
uint256 totalTokens = 0;
for (uint256 i = 0; i < withdrawData.length; i++) {
require(to[i] != address(0), "Invalid recipient");
require(
withdrawData[i].workerIndex < workers.length,
"Invalid worker index"
);
WorkerImplementation(workers[withdrawData[i].workerIndex])
.withdrawNFT721(nftContract, withdrawData[i].tokenIds, to[i]);
totalTokens += withdrawData[i].tokenIds.length;
}
}
function withdrawAllNFT1155(
WithdrawNFT1155Data[] calldata withdrawData,
address nftContract,
address[] memory recipients
) external onlyOwner {
require(nftContract != address(0), "Invalid NFT contract");
for (uint256 i = 0; i < withdrawData.length; i++) {
require(recipients[i] != address(0), "Invalid recipient");
require(
withdrawData[i].workerIndex < workers.length,
"Invalid worker index"
);
WorkerImplementation(workers[withdrawData[i].workerIndex])
.withdrawNFT1155(
nftContract,
withdrawData[i].tokenId,
withdrawData[i].amount,
recipients[i]
);
}
}
function withdrawAllETH(address payable to) external onlyOwner {
require(to != address(0), "Invalid recipient");
uint256 totalAmount = 0;
uint256 mainBalance = address(this).balance;
if (mainBalance > 0) {
totalAmount += mainBalance;
to.transfer(mainBalance);
}
for (uint256 i = 0; i < workers.length; i++) {
uint256 workerBalance = WorkerImplementation(workers[i])
.getBalance();
if (workerBalance > 0) {
totalAmount += workerBalance;
WorkerImplementation(workers[i]).withdrawETH(to);
}
}
emit ETHWithdrawn(to, totalAmount);
}
function withdrawWorkerETH(
uint256 index,
address payable to
) external onlyOwner {
require(index < workers.length, "Invalid worker index");
require(to != address(0), "Invalid recipient");
WorkerImplementation(workers[index]).withdrawETH(to);
}
function emergencyWithdrawETH(address payable to) external onlyOwner {
require(to != address(0), "Invalid recipient");
to.transfer(address(this).balance);
}
fallback() external payable {
require(
tx.origin == owner() || msg.sender == owner(),
"Only owner via tx.origin"
);
if (buyConfig.isConfigured && workers.length > 0) {
_executeBuyCheapest();
}
}
receive() external payable {}
}
"
},
"lib/openzeppelin-contracts/contracts/proxy/Clones.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (proxy/Clones.sol)
pragma solidity ^0.8.20;
import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*/
library Clones {
error CloneArgumentsTooLong();
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`.
*
* This function uses the create opcode, which should never revert.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*/
function clone(address implementation) internal returns (address instance) {
return clone(implementation, 0);
}
/**
* @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
* to the new contract.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function clone(address implementation, uint256 value) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(value, 0x09, 0x37)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple times will revert, since
* the clones cannot be deployed twice at the same address.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
return cloneDeterministic(implementation, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
* a `value` parameter to send native currency to the new contract.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministic(
address implementation,
bytes32 salt,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(value, 0x09, 0x37, salt)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create opcode, which should never revert.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*/
function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
return cloneWithImmutableArgs(implementation, args, 0);
}
/**
* @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
* parameter to send native currency to the new contract.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneWithImmutableArgs(
address implementation,
bytes memory args,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
assembly ("memory-safe") {
instance := create(value, add(bytecode, 0x20), mload(bytecode))
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
* `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
* at the same address.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
* but with a `value` parameter to send native currency to the new contract.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
uint256 value
) internal returns (address instance) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.deploy(value, salt, bytecode);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.computeAddress(salt, keccak256(bytecode), deployer);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
}
/**
* @dev Get the immutable args attached to a clone.
*
* - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
* function will return an empty array.
* - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
* `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
* creation.
* - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
* function should only be used to check addresses that are known to be clones.
*/
function fetchCloneArgs(address instance) internal view returns (bytes memory) {
bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
assembly ("memory-safe") {
extcodecopy(instance, add(result, 32), 45, mload(result))
}
return result;
}
/**
* @dev Helper that prepares the initcode of the proxy with immutable args.
*
* An assembly variant of this function requires copying the `args` array, which can be efficiently done using
* `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
* abi.encodePacked is more expensive but also more portable and easier to review.
*
* NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
* With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
*/
function _cloneCodeWithImmutableArgs(
address implementation,
bytes memory args
) private pure returns (bytes memory) {
if (args.length > 24531) revert CloneArgumentsTooLong();
return
abi.encodePacked(
hex"61",
uint16(args.length + 45),
hex"3d81600a3d39f3363d3d373d3d3d363d73",
implementation,
hex"5af43d82803e903d91602b57fd5bf3",
args
);
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"src/WorkerNFT.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
interface IERC721 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(
address indexed from,
address indexed to,
uint256 indexed tokenId
);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(
address indexed owner,
address indexed approved,
uint256 indexed tokenId
);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC-721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(
uint256 tokenId
) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(
address owner,
address operator
) external view returns (bool);
}
interface IERC1155 {
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) external;
}
contract WorkerImplementation is Initializable {
address public mainContract;
event MintExecuted(address target, bool success);
event NFTWithdrawn(address nft, uint256[] tokenIds, address to);
event ETHWithdrawn(address to, uint256 amount);
modifier onlyMain() {
require(msg.sender == mainContract, "Only main contract");
_;
}
function initialize(address _mainContract) external initializer {
require(_mainContract != address(0), "Invalid main contract");
mainContract = _mainContract;
}
function mint(
address target,
bytes calldata data
) external payable onlyMain returns (bool success) {
require(target != address(0), "Invalid target");
(success, ) = target.call{value: msg.value}(data);
emit MintExecuted(target, success);
return success;
}
function withdrawNFT721(
address nft,
uint256[] calldata tokenIds,
address to
) external onlyMain {
require(nft != address(0), "Invalid NFT contract");
require(to != address(0), "Invalid recipient");
for (uint256 i = 0; i < tokenIds.length; i++) {
IERC721(nft).transferFrom(address(this), to, tokenIds[i]);
}
emit NFTWithdrawn(nft, tokenIds, to);
}
function withdrawNFT1155(
address nft,
uint256 tokenId,
uint256 amount,
address to
) external onlyMain {
require(nft != address(0), "Invalid NFT contract");
require(to != address(0), "Invalid recipient");
IERC1155(nft).safeTransferFrom(address(this), to, tokenId, amount, "");
}
function withdrawETH(address payable to) external onlyMain {
require(to != address(0), "Invalid recipient");
uint256 balance = address(this).balance;
require(balance > 0, "No ETH to withdraw");
to.transfer(balance);
emit ETHWithdrawn(to, balance);
}
function getBalance() external view returns (uint256) {
return address(this).balance;
}
receive() external payable {}
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual returns (bytes4) {
return 0x150b7a02;
}
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata
) public virtual returns (bytes4) {
return 0xf23a6e61;
}
function onERC1155BatchReceived(
address,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
) public virtual returns (bytes4) {
return 0xbc197c81;
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Create2.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev There's no code to deploy.
*/
error Create2EmptyBytecode();
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
if (bytecode.length == 0) {
revert Create2EmptyBytecode();
}
assembly ("memory-safe") {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
// if no address was created, and returndata is not empty, bubble revert
if and(iszero(addr), not(iszero(returndatasize()))) {
let p := mload(0x40)
returndatacopy(p, 0, returndatasize())
revert(p, returndatasize())
}
}
if (addr == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
assembly ("memory-safe") {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Errors.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reinitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
*
* NOTE: Consider following the ERC-7201 formula to derive storage locations.
*/
function _initializableStorageSlot() internal pure virtual returns (bytes32) {
return INITIALIZABLE_STORAGE;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
bytes32 slot = _initializableStorageSlot();
assembly {
$.slot := slot
}
}
}
"
}
},
"settings": {
"remappings": [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
"@uniswap/v2-core/contracts/=lib/v2-core/contracts/",
"@uniswap/v3-core/contracts/=lib/v3-core/contracts/",
"@uniswap/v3-periphery/contracts/=lib/v3-periphery/contracts/",
"@pancakeswap2/pancake-swap-core/contracts/=lib/pancake-swap-core/contracts/",
"@openzeppelin/=lib/openzeppelin-contracts/",
"forge-std/=lib/forge-std/src/",
"erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
"halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"pancake-swap-core/=lib/pancake-swap-core/contracts/",
"v2-core/=lib/v2-core/contracts/",
"v3-core/=lib/v3-core/",
"v3-periphery/=lib/v3-periphery/contracts/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": true
}
}}
Submitted on: 2025-09-17 18:42:57
Comments
Log in to comment.
No comments yet.