编译器
0.8.26+commit.8a97fa7a
文件 1 的 7:Address.sol
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
library Address {
error AddressEmptyCode(address target);
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, bytes memory returndata) = recipient.call{value: amount}("");
if (!success) {
_revert(returndata);
}
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
function _revert(bytes memory returndata) private pure {
if (returndata.length > 0) {
assembly ("memory-safe") {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}
文件 2 的 7:Context.sol
pragma solidity ^0.8.20;
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;
}
}
文件 3 的 7:Errors.sol
pragma solidity ^0.8.20;
library Errors {
error InsufficientBalance(uint256 balance, uint256 needed);
error FailedCall();
error FailedDeployment();
error MissingPrecompile(address);
}
文件 4 的 7:Ownable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 5 的 7:Pausable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Pausable is Context {
event Paused(address account);
event Unpaused(address account);
bool private _paused;
constructor() {
_paused = false;
}
modifier whenNotPaused() {
_requireNotPaused();
_;
}
modifier whenPaused() {
_requirePaused();
_;
}
function paused() public view virtual returns (bool) {
return _paused;
}
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
文件 6 的 7:ReentrancyGuard.sol
pragma solidity ^0.8.0;
abstract contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
}
function _nonReentrantAfter() private {
_status = _NOT_ENTERED;
}
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
文件 7 的 7:SayGm.sol
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
contract SayGM is Ownable, ReentrancyGuard, Pausable {
using Address for address payable;
mapping(address => uint256) public lastGm;
mapping(address => uint256) public totalGMs;
mapping(address => uint256) public longestStreak;
mapping(address => uint256) public currentStreak;
struct UserStats {
address userAddress;
uint256 currentStreak;
uint256 longestStreak;
uint256 totalGMs;
}
UserStats[] public leaderboard;
mapping(address => uint256) private leaderboardIndex;
uint256 public constant MAX_LEADERBOARD_SIZE = 100;
address public feeRecipient;
uint256 public gmFee;
uint256 public timeLimit;
event SayGMEvent(
address indexed sender,
address indexed receiver,
string message,
uint256 timestamp
);
event FeeRecipientUpdated(address indexed previousRecipient, address indexed newRecipient);
event GMFeeUpdated(uint256 previousFee, uint256 newFee);
event TimeLimitUpdated(uint256 previousTimeLimit, uint256 newTimeLimit);
event FeesWithdrawn(address indexed recipient, uint256 amount);
event StreakUpdated(address indexed user, uint256 newStreak);
event NewLongestStreak(address indexed user, uint256 newRecord);
event LeaderboardUpdated(address indexed user, uint256 rank);
constructor(address _feeRecipient) Ownable(msg.sender) {
require(_feeRecipient != address(0), "SayGM: Invalid fee recipient");
feeRecipient = _feeRecipient;
gmFee = 0.000029 ether;
timeLimit = 24 hours;
}
function sayGM(string calldata message) external payable nonReentrant whenNotPaused {
require(msg.value == gmFee, "SayGM: Incorrect ETH fee");
require(block.timestamp >= lastGm[msg.sender] + timeLimit, "SayGM: Wait before sending another GM");
_processGM(msg.sender, msg.sender, message);
}
function sayGMTo(address recipient, string calldata message) external payable nonReentrant whenNotPaused {
require(msg.value == gmFee, "SayGM: Incorrect ETH fee");
require(recipient != address(0), "SayGM: Cannot send to zero address");
require(block.timestamp >= lastGm[msg.sender] + timeLimit, "SayGM: Wait before sending another GM");
_processGM(msg.sender, recipient, message);
}
function _processGM(address sender, address recipient, string calldata message) internal {
uint256 previousGmTime = lastGm[sender];
_updateStreak(sender, previousGmTime);
lastGm[sender] = block.timestamp;
_updateLeaderboard(sender);
emit SayGMEvent(sender, recipient, message, block.timestamp);
}
function _updateStreak(address user, uint256 previousGmTime) internal {
uint256 currentTime = block.timestamp;
if (totalGMs[user] == 0) {
currentStreak[user] = 1;
longestStreak[user] = 1;
} else {
uint256 daysSinceLastGm = (currentTime - previousGmTime) / 86400;
if (daysSinceLastGm <= 1) {
currentStreak[user]++;
if (currentStreak[user] > longestStreak[user]) {
longestStreak[user] = currentStreak[user];
emit NewLongestStreak(user, longestStreak[user]);
}
} else {
currentStreak[user] = 1;
}
}
totalGMs[user]++;
emit StreakUpdated(user, currentStreak[user]);
}
function _updateLeaderboard(address user) internal {
UserStats memory stats = UserStats({
userAddress: user,
currentStreak: currentStreak[user],
longestStreak: longestStreak[user],
totalGMs: totalGMs[user]
});
if (leaderboardIndex[user] == 0) {
if (leaderboard.length < MAX_LEADERBOARD_SIZE) {
leaderboard.push(stats);
leaderboardIndex[user] = leaderboard.length;
_sortLeaderboard();
} else {
if (stats.currentStreak > leaderboard[leaderboard.length - 1].currentStreak) {
address replacedUser = leaderboard[leaderboard.length - 1].userAddress;
leaderboard[leaderboard.length - 1] = stats;
leaderboardIndex[replacedUser] = 0;
leaderboardIndex[user] = leaderboard.length;
_sortLeaderboard();
}
}
} else {
uint256 idx = leaderboardIndex[user] - 1;
leaderboard[idx] = stats;
_sortLeaderboard();
}
emit LeaderboardUpdated(user, leaderboardIndex[user]);
}
function _sortLeaderboard() internal {
uint256 n = leaderboard.length;
for (uint256 i = 0; i < n - 1; i++) {
for (uint256 j = 0; j < n - i - 1; j++) {
if (leaderboard[j].currentStreak < leaderboard[j + 1].currentStreak) {
UserStats memory temp = leaderboard[j];
leaderboard[j] = leaderboard[j + 1];
leaderboard[j + 1] = temp;
leaderboardIndex[leaderboard[j].userAddress] = j + 1;
leaderboardIndex[leaderboard[j + 1].userAddress] = j + 2;
}
}
}
}
function getTopUsers(uint256 n) external view returns (UserStats[] memory) {
require(n > 0 && n <= leaderboard.length, "SayGM: Invalid number of users requested");
UserStats[] memory topUsers = new UserStats[](n);
for (uint256 i = 0; i < n; i++) {
topUsers[i] = leaderboard[i];
}
return topUsers;
}
function getUserStats(address user) external view returns (UserStats memory) {
return UserStats({
userAddress: user,
currentStreak: currentStreak[user],
longestStreak: longestStreak[user],
totalGMs: totalGMs[user]
});
}
function getUserRank(address user) external view returns (uint256) {
uint256 index = leaderboardIndex[user];
require(index > 0, "SayGM: User not in leaderboard");
return index;
}
function setFeeRecipient(address newFeeRecipient) external onlyOwner {
require(newFeeRecipient != address(0), "SayGM: Invalid fee recipient");
emit FeeRecipientUpdated(feeRecipient, newFeeRecipient);
feeRecipient = newFeeRecipient;
}
function setGmFee(uint256 newGmFee) external onlyOwner {
emit GMFeeUpdated(gmFee, newGmFee);
gmFee = newGmFee;
}
function setTimeLimit(uint256 newTimeLimit) external onlyOwner {
require(newTimeLimit > 0, "SayGM: Time limit must be greater than 0");
emit TimeLimitUpdated(timeLimit, newTimeLimit);
timeLimit = newTimeLimit;
}
function withdrawFees() external onlyOwner nonReentrant {
uint256 amount = address(this).balance;
require(amount > 0, "SayGM: No fees to withdraw");
payable(feeRecipient).sendValue(amount);
emit FeesWithdrawn(feeRecipient, amount);
}
function contractBalance() external view returns (uint256) {
return address(this).balance;
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
receive() external payable {
revert("SayGM: Direct ETH transfers not allowed");
}
}
{
"compilationTarget": {
"contracts/SayGm.sol": "SayGM"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_feeRecipient","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"FailedCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousRecipient","type":"address"},{"indexed":true,"internalType":"address","name":"newRecipient","type":"address"}],"name":"FeeRecipientUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeesWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"GMFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"rank","type":"uint256"}],"name":"LeaderboardUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"newRecord","type":"uint256"}],"name":"NewLongestStreak","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"string","name":"message","type":"string"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"SayGMEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"newStreak","type":"uint256"}],"name":"StreakUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousTimeLimit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTimeLimit","type":"uint256"}],"name":"TimeLimitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"MAX_LEADERBOARD_SIZE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contractBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"currentStreak","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"n","type":"uint256"}],"name":"getTopUsers","outputs":[{"components":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"uint256","name":"currentStreak","type":"uint256"},{"internalType":"uint256","name":"longestStreak","type":"uint256"},{"internalType":"uint256","name":"totalGMs","type":"uint256"}],"internalType":"struct SayGM.UserStats[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserRank","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserStats","outputs":[{"components":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"uint256","name":"currentStreak","type":"uint256"},{"internalType":"uint256","name":"longestStreak","type":"uint256"},{"internalType":"uint256","name":"totalGMs","type":"uint256"}],"internalType":"struct SayGM.UserStats","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gmFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"lastGm","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"leaderboard","outputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"uint256","name":"currentStreak","type":"uint256"},{"internalType":"uint256","name":"longestStreak","type":"uint256"},{"internalType":"uint256","name":"totalGMs","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"longestStreak","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"message","type":"string"}],"name":"sayGM","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"string","name":"message","type":"string"}],"name":"sayGMTo","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newFeeRecipient","type":"address"}],"name":"setFeeRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newGmFee","type":"uint256"}],"name":"setGmFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newTimeLimit","type":"uint256"}],"name":"setTimeLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"timeLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"totalGMs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]