/**
*Submitted for verification at goerli.basescan.org on 2023-11-16
*/
// SPDX-License-Identifier: MIT
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;
}
}
abstract contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(msg.sender);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
require(owner() == msg.sender, "Ownable: caller is not the owner");
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
contract Light is Ownable, ReentrancyGuard {
struct Seat {
address owner;
uint256 totalReward;
uint256 currentReward;
address layer1Address;
uint256 deadline;
uint256 subjectETHBalance;
uint256 totalSupply;
mapping (address => uint256) balanceOf;
mapping (uint256 => uint256) claimedBitMap;
}
struct TradeEvent {
uint256 eventIndex;
uint256 ts;
address trader;
address subject;
bool isBuy;
uint256 buyAmount;
uint256 ethAmount;
uint256 traderBalance;
uint256 supply;
}
struct ClaimEvent {
uint256 eventIndex;
uint256 ts;
address sender;
address subject;
uint256 claimedIndex;
uint256 reward;
}
struct SubjectRewardSetEvent {
uint256 eventIndex;
uint256 ts;
bytes32 subject;
address owner;
uint256 snapshotReward;
uint256 rewardPercent;
uint256 totalReward;
}
address public protocolFeeTo;
uint256 public tradeIndex;
uint256 public bindIndex;
uint256 public prizeIndex;
uint256 public claimIndex;
uint256 public protocolFeePercent;
uint256 public subjectFeePercent;
address[] public signers;
mapping (address => bool) public authorized;
mapping (address => uint256) public indexes;
mapping (address => Seat) public seats;
event SignerAdded(address sender, address account);
event SignerRemoved(address sender, address account);
event BindSubject(uint256 eventIndex, uint256 ts, address layer1Address, address owner);
event RewardClaimed(ClaimEvent claimEvent);
event SubjectRewardSet(SubjectRewardSetEvent rewardEvent);
event Trade(TradeEvent tradeEvent);
constructor() {
protocolFeeTo = msg.sender;
protocolFeePercent = 0.05 ether; // 5%
subjectFeePercent = 0.05 ether; // 5%
uint256 chainId;
assembly {
chainId := chainid()
}
}
struct TradeParameters {
uint256 value;
uint256 price;
uint256 protocolFee;
uint256 subjectFee;
bool success;
}
function buySeat(
address layer1Address,
uint256 amount
) external payable nonReentrant {
TradeParameters memory params;
Seat storage seat = seats[layer1Address];
params.price = getPrice(seat.totalSupply, amount);
params.protocolFee = params.price * protocolFeePercent / 1 ether;
params.subjectFee = params.price * subjectFeePercent / 1 ether;
params.value = params.price + params.protocolFee + params.subjectFee;
require(msg.value >= params.value, "Insufficient payment");
seat.balanceOf[msg.sender] += amount;
seat.totalSupply += amount;
if (seat.owner == address(0)) {
seat.subjectETHBalance += params.subjectFee;
} else {
(params.success, ) = seat.owner.call{value: params.subjectFee}(new bytes(0));
require(params.success, "Unable to send funds");
}
(params.success, ) = protocolFeeTo.call{value: params.protocolFee}(new bytes(0));
require(params.success, "Unable to send funds");
emit Trade(TradeEvent({
eventIndex: tradeIndex++,
ts: block.timestamp,
trader: msg.sender,
subject: layer1Address,
isBuy: true,
buyAmount: amount,
ethAmount: params.price,
traderBalance: seat.balanceOf[msg.sender],
supply: seat.totalSupply
}));
}
function sellSeat(
address layer1Address,
uint256 amount
) external nonReentrant {
TradeParameters memory params;
Seat storage seat = seats[layer1Address];
require(seat.balanceOf[msg.sender] >= amount, "Insufficient seats");
params.price = getPrice(seat.totalSupply - amount, amount);
params.protocolFee = params.price * protocolFeePercent / 1 ether;
params.subjectFee = params.price * subjectFeePercent / 1 ether;
params.value = params.price - params.protocolFee - params.subjectFee;
seat.balanceOf[msg.sender] -= amount;
seat.totalSupply -= amount;
if (seat.owner == address(0)) {
seat.subjectETHBalance += params.subjectFee;
} else {
(params.success, ) = seat.owner.call{value: params.subjectFee}(new bytes(0));
require(params.success, "Unable to send funds");
}
(params.success, ) = msg.sender.call{value: params.value}(new bytes(0));
require(params.success, "Unable to send funds");
(params.success, ) = protocolFeeTo.call{value: params.protocolFee}(new bytes(0));
require(params.success, "Unable to send funds");
emit Trade(TradeEvent({
eventIndex: tradeIndex++,
ts: block.timestamp,
trader: msg.sender,
subject: layer1Address,
isBuy: false,
buyAmount: amount,
ethAmount: params.price,
traderBalance: seat.balanceOf[msg.sender],
supply: seat.totalSupply
}));
}
function bindSubjectAndClaim(
address layer1Address,
address owner
) external onlyOwner {
// recover(buildBindSeparator(subject, owner), v, r, s);
Seat storage vp = seats[layer1Address];
if (vp.owner == address(0)) {
vp.owner = owner;
emit BindSubject(bindIndex++, block.timestamp, layer1Address, owner);
}
if (vp.subjectETHBalance > 0) {
uint256 balance = vp.subjectETHBalance;
vp.subjectETHBalance = 0;
(bool success, ) = owner.call{value: balance}(new bytes(0));
require(success, "Unable to send funds");
emit RewardClaimed(ClaimEvent({
eventIndex: claimIndex++,
ts: block.timestamp,
sender: owner,
subject: layer1Address,
claimedIndex: 0,
reward: balance
}));
}
}
function setProtocolFeeTo(address feeTo) external onlyOwner {
protocolFeeTo = feeTo;
}
function setProtocolFeePercent(uint256 feePercent) external onlyOwner {
protocolFeePercent = feePercent;
}
function setSubjectFeePercent(uint256 feePercent) external onlyOwner {
subjectFeePercent = feePercent;
}
function getPrice(uint256 supply, uint256 amount) public pure returns (uint256) {
uint256 sum1 = supply * (supply + 1) * (2 * supply + 1) / 6;
uint256 sum2 = (supply + amount) * (supply + 1 + amount) * (2 * (supply + amount) + 1) / 6;
uint256 summation = sum2 - sum1;
return summation * 1 ether / 54321;
}
function getBuyPrice(address layer1Address, uint256 amount) public view returns (uint256) {
return getPrice(seats[layer1Address].totalSupply, amount);
}
function getSellPrice(address layer1Address, uint256 amount) public view returns (uint256) {
return getPrice(seats[layer1Address].totalSupply - amount, amount);
}
function getBuyPriceAfterFee(address layer1Address, uint256 amount) public view returns (uint256) {
uint256 price = getBuyPrice(layer1Address, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
return price + protocolFee + subjectFee;
}
function getSellPriceAfterFee(address layer1Address, uint256 amount) public view returns (uint256) {
uint256 price = getSellPrice(layer1Address, amount);
uint256 protocolFee = price * protocolFeePercent / 1 ether;
uint256 subjectFee = price * subjectFeePercent / 1 ether;
return price - protocolFee - subjectFee;
}
function getSubjectOwner(address layer1Address) public view returns (address) {
return seats[layer1Address].owner;
}
function getSubjectETHBalance(address layer1Address) public view returns (uint256) {
return seats[layer1Address].subjectETHBalance;
}
function getSubjectSupply(address layer1Address) public view returns (uint256) {
return seats[layer1Address].totalSupply;
}
function getSubjectBalanceOf(address layer1Address, address account) public view returns (uint256) {
return seats[layer1Address].balanceOf[account];
}
function getSubjectTotalReward(address layer1Address) public view returns (uint256) {
return seats[layer1Address].totalReward;
}
function getSubjectCurrentReward(address layer1Address) public view returns (uint256) {
return seats[layer1Address].currentReward;
}
function getLightStatus(address layer1Address) public view returns (bool) {
Seat storage vp = seats[layer1Address];
if (vp.owner == address(0)) {
return false;
} else {
return true;
}
}
function getSubjectDeadline(address layer1Address) public view returns (uint256) {
return seats[layer1Address].deadline;
}
}
{
"compilationTarget": {
"Light.sol": "Light"
},
"evmVersion": "cancun",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"eventIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"ts","type":"uint256"},{"indexed":false,"internalType":"address","name":"layer1Address","type":"address"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"BindSubject","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":[{"components":[{"internalType":"uint256","name":"eventIndex","type":"uint256"},{"internalType":"uint256","name":"ts","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"subject","type":"address"},{"internalType":"uint256","name":"claimedIndex","type":"uint256"},{"internalType":"uint256","name":"reward","type":"uint256"}],"indexed":false,"internalType":"struct Light.ClaimEvent","name":"claimEvent","type":"tuple"}],"name":"RewardClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"SignerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"SignerRemoved","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"eventIndex","type":"uint256"},{"internalType":"uint256","name":"ts","type":"uint256"},{"internalType":"bytes32","name":"subject","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"snapshotReward","type":"uint256"},{"internalType":"uint256","name":"rewardPercent","type":"uint256"},{"internalType":"uint256","name":"totalReward","type":"uint256"}],"indexed":false,"internalType":"struct Light.SubjectRewardSetEvent","name":"rewardEvent","type":"tuple"}],"name":"SubjectRewardSet","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"eventIndex","type":"uint256"},{"internalType":"uint256","name":"ts","type":"uint256"},{"internalType":"address","name":"trader","type":"address"},{"internalType":"address","name":"subject","type":"address"},{"internalType":"bool","name":"isBuy","type":"bool"},{"internalType":"uint256","name":"buyAmount","type":"uint256"},{"internalType":"uint256","name":"ethAmount","type":"uint256"},{"internalType":"uint256","name":"traderBalance","type":"uint256"},{"internalType":"uint256","name":"supply","type":"uint256"}],"indexed":false,"internalType":"struct Light.TradeEvent","name":"tradeEvent","type":"tuple"}],"name":"Trade","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"authorized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bindIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"bindSubjectAndClaim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"buySeat","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"claimIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getBuyPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getBuyPriceAfterFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"}],"name":"getLightStatus","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"supply","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getSellPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getSellPriceAfterFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"},{"internalType":"address","name":"account","type":"address"}],"name":"getSubjectBalanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"}],"name":"getSubjectCurrentReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"}],"name":"getSubjectDeadline","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"}],"name":"getSubjectETHBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"}],"name":"getSubjectOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"}],"name":"getSubjectSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"}],"name":"getSubjectTotalReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"indexes","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":"prizeIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFeePercent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFeeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"seats","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"totalReward","type":"uint256"},{"internalType":"uint256","name":"currentReward","type":"uint256"},{"internalType":"address","name":"layer1Address","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"subjectETHBalance","type":"uint256"},{"internalType":"uint256","name":"totalSupply","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"layer1Address","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"sellSeat","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"feePercent","type":"uint256"}],"name":"setProtocolFeePercent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"feeTo","type":"address"}],"name":"setProtocolFeeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"feePercent","type":"uint256"}],"name":"setSubjectFeePercent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"signers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"subjectFeePercent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tradeIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]