Contract Name:
PlutocatsMultiTool
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: GPL-3.0
/// @title Interface for Blast predeploy
pragma solidity >=0.8.0;
enum YieldMode {
AUTOMATIC,
VOID,
CLAIMABLE
}
enum GasMode {
VOID,
CLAIMABLE
}
interface IBlast {
// configure
function configureContract(address contractAddress, YieldMode _yield, GasMode gasMode, address governor) external;
function configure(YieldMode _yield, GasMode gasMode, address governor) external;
// base configuration options
function configureClaimableYield() external;
function configureClaimableYieldOnBehalf(address contractAddress) external;
function configureAutomaticYield() external;
function configureAutomaticYieldOnBehalf(address contractAddress) external;
function configureVoidYield() external;
function configureVoidYieldOnBehalf(address contractAddress) external;
function configureClaimableGas() external;
function configureClaimableGasOnBehalf(address contractAddress) external;
function configureVoidGas() external;
function configureVoidGasOnBehalf(address contractAddress) external;
function configureGovernor(address _governor) external;
function configureGovernorOnBehalf(address _newGovernor, address contractAddress) external;
// claim yield
function claimYield(address contractAddress, address recipientOfYield, uint256 amount) external returns (uint256);
function claimAllYield(address contractAddress, address recipientOfYield) external returns (uint256);
// claim gas
function claimAllGas(address contractAddress, address recipientOfGas) external returns (uint256);
function claimGasAtMinClaimRate(address contractAddress, address recipientOfGas, uint256 minClaimRateBips)
external
returns (uint256);
function claimMaxGas(address contractAddress, address recipientOfGas) external returns (uint256);
function claimGas(address contractAddress, address recipientOfGas, uint256 gasToClaim, uint256 gasSecondsToConsume)
external
returns (uint256);
// read functions
function readClaimableYield(address contractAddress) external view returns (uint256);
function readYieldConfiguration(address contractAddress) external view returns (uint8);
function readGasParams(address contractAddress)
external
view
returns (uint256 etherSeconds, uint256 etherBalance, uint256 lastUpdated, GasMode);
}
// SPDX-License-Identifier: GPL-3.0
/// @title An interface for Plutocats Token
pragma solidity >=0.8.0;
interface IPlutocatsTokenMultiTool {
function getPrice() external view returns (uint256);
function mint() external payable returns (uint256);
function transferFrom(address from, address to, uint256 tokenId) external;
function getVRGDAPrice(int256 timeSinceStart, uint256 sold) external view returns (uint256);
// solhint-disable-next-line func-name-mixedcase
function MINT_START() external view returns (uint256);
function adjustedTotalSupply() external view returns (uint256);
function totalSupply() external view returns (uint256);
function setApprovalForAll(address operator, bool approved) external;
function balanceOf(address owner) external view returns (uint256);
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
}
// SPDX-License-Identifier: GPL-3.0
/// @title Interface for Plutocats Reserve
pragma solidity >=0.8.0;
interface IReserve {
event Quit(address indexed msgSender, uint256 amount, uint256[] tokenIds);
event SetBlastGovernor(address indexed governor);
error NoCirculatingSupply();
function setGovernor(address _governor) external;
function quit(uint256[] calldata tokenIds) external;
}
// SPDX-License-Identifier: GPL-3.0
/// This utility is not part of the Plutocats protocol and was created to provide
/// a way to mint Plutocats at market price under high demand.
pragma solidity >=0.8.0;
import {IPlutocatsTokenMultiTool} from "../interfaces/IPlutocatsTokenMultiTool.sol";
import {IBlast} from "../interfaces/IBlast.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {IReserve} from "../interfaces/IReserve.sol";
contract PlutocatsMultiTool {
using Address for address payable;
event Minted(uint256 tokenId, address owner, uint256 price);
error PaymentTooLow();
/// The address of the pre-deployed Blast contract.
address public constant BLAST_PREDEPLOY_ADDRESS = 0x4300000000000000000000000000000000000002;
address public reserve;
IBlast public blast;
IPlutocatsTokenMultiTool public plutocats;
constructor(address _plutocats, address _reserve) {
plutocats = IPlutocatsTokenMultiTool(_plutocats);
blast = IBlast(BLAST_PREDEPLOY_ADDRESS);
blast.configureClaimableGas();
blast.configureGovernor(msg.sender);
reserve = _reserve;
plutocats.setApprovalForAll(address(reserve), true);
}
/// @dev from solmate
/// @dev Takes an integer amount of seconds and converts it to a wad amount of days.
/// @dev Will not revert on overflow, only use where overflow is not possible.
/// @dev Not meant for negative second amounts, it assumes x is positive.
function _toDaysWadUnsafe(uint256 x) internal pure returns (int256 r) {
// solhint-disable-next-line no-inline-assembly
assembly {
// Multiply x by 1e18 and then divide it by 86400.
r := div(mul(x, 1000000000000000000), 86400)
}
}
function estimateMaxPricePer(uint256 amount) public view returns (uint256) {
if(amount == 0){
return 0;
}
if(amount == 1){
return plutocats.getPrice();
}
uint256 timeSinceStart = block.timestamp - plutocats.MINT_START();
uint256 totalSupply = plutocats.totalSupply();
uint256 adjTotalSupply = plutocats.adjustedTotalSupply();
return _estimatePriceForN(amount, timeSinceStart, totalSupply, adjTotalSupply);
}
function _estimatePriceForN(uint256 n, uint256 timeSinceStart, uint256 totalSupply, uint256 adjTotalSupply) internal view returns (uint256) {
uint256 vrgdaPrice = plutocats.getVRGDAPrice(_toDaysWadUnsafe(timeSinceStart), totalSupply + n);
uint256 currentMinPrice = reserve.balance / adjTotalSupply;
uint256 minPrice = (reserve.balance + ((n-1)*currentMinPrice)) / (adjTotalSupply + n - 1);
if (vrgdaPrice < currentMinPrice) {
return minPrice;
}
return vrgdaPrice;
}
function estimateTotalCost(uint256 amount) public view returns (uint256 totalCost) {
if(amount == 0){
return 0;
}
if(amount == 1){
return plutocats.getPrice();
}
uint256 timeSinceStart = block.timestamp - plutocats.MINT_START();
uint256 totalSupply = plutocats.totalSupply();
uint256 adjTotalSupply = plutocats.adjustedTotalSupply();
for(uint256 n = 1; n <= amount; n++){
uint256 price = _estimatePriceForN(n, timeSinceStart, totalSupply, adjTotalSupply);
totalCost += price;
}
return totalCost;
}
function estimateMaxAtCurrentPrice() public view returns (uint256) {
uint256 price = plutocats.getPrice();
uint256 priceAt = price;
uint256 count = 1;
while(priceAt <= price) {
count++;
priceAt = estimateMaxPricePer(count);
}
return count;
}
function recycleMultiple(uint256 amount) external payable {
uint256 price = plutocats.getPrice();
uint256[] memory mintedId = new uint256[](1);
IReserve reserveContract = IReserve(reserve);
for(uint256 i = 0; i < amount; i++) {
if(price > msg.value) {
payable(msg.sender).sendValue(msg.value);
return;
}
mintedId[0] = plutocats.mint{value: price}();
reserveContract.quit(mintedId);
price = plutocats.getPrice();
}
payable(msg.sender).sendValue(msg.value);
}
function buyMultiple(uint256 amount) external payable returns (uint256 firstMintedId, uint256 lastMintedId, uint256 totalPrice) {
uint256 price;
for(uint256 i = 0; i < amount; i++) {
price = plutocats.getPrice();
totalPrice += price;
lastMintedId = plutocats.mint{value: price}();
if (i == 0) {
firstMintedId = lastMintedId;
}
emit Minted(lastMintedId, msg.sender, price);
plutocats.transferFrom(address(this), msg.sender, lastMintedId);
}
require(msg.value >= totalPrice, "payment too low");
uint256 refund = msg.value - totalPrice;
if (refund > 0) {
payable(msg.sender).sendValue(refund);
}
return (firstMintedId, lastMintedId, totalPrice);
}
function buy() external payable returns (uint256, uint256) {
uint256 price = plutocats.getPrice();
require(msg.value >= price, "payment too low");
uint256 mintedId = plutocats.mint{value: price}();
emit Minted(mintedId, msg.sender, price);
plutocats.transferFrom(address(this), msg.sender, mintedId);
uint256 refund = msg.value - price;
if (refund > 0) {
payable(msg.sender).sendValue(refund);
}
return (mintedId, price);
}
function getTokensOwnedBy(address owner) external view returns (uint256[] memory) {
uint256 ownerBalance = plutocats.balanceOf(owner);
uint256[] memory tokens = new uint256[](ownerBalance);
for(uint256 i = 0; i < ownerBalance; i++){
tokens[i] = plutocats.tokenOfOwnerByIndex(owner, i);
}
return tokens;
}
function quitValue() external view returns (uint256) {
return reserve.balance / plutocats.adjustedTotalSupply();
}
receive() external payable {}
fallback() external payable {}
}