diff --git a/src/HarbergerTaxModule.sol b/src/HarbergerTaxModule.sol new file mode 100644 index 0000000..940046f --- /dev/null +++ b/src/HarbergerTaxModule.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: UNLICENSED + + +/* + + !!! DO NOT USE THIS CONTRACT IN PRODUCTION !!! + + This is exists purely as a thought experiment. No tests have been written, + so actual functionality may be different than what is advertised. It might + not even compile, for all I know + + + HARBERGER TAX + + - The Leader, at any point, may choose the SequencerAddress + - The Leader must set a LeadershipPrice at which they are willing to sell the Leadership + - At any point, another address may purchase the Leadership for that price + - The Leader must pay a tax equal to 5% of the LeadershipPrice, annualized + paid weekly, + to retain the Leadership. + - All taxes go to the Treasury + +*/ + + +pragma solidity 0.8.25; + +import {IsAllowed} from "src/interfaces/IsAllowed.sol"; + + +contract HarbergerTaxModule is IsAllowed { + address public Leader; + address public SequencerAddress; + address public Treasury; + uint256 public LeadershipPrice + + uint256 public taxLastpaid; + uint256 public lastPriceUpdate; + + uint256 constant public taxRateBps = 500; + + constructor(address treasury) { + Treasury = treasury; + } + + function setLeadershipPrice(uint256 price) external { + require(msg.sender == Leader, 'Only the Leader can set the LeadershipPrice') + LeadershipPrice = price; + lastPriceUpdate = block.timestamp; + } + + function buyLeadership() external payable { + require(msg.value >= LeadershipPrice, 'Payment amount too low'); + + (bool sent,) = payable(Leader).call{value: msg.value}(''); + require(sent, 'Failed to send'); + + Leader = msg.sender; + } + + function payTax() external payable { + require(msg.value >= LeadershipPrice * taxRateBps / 520000, 'Tax not high enough'); + require(block.timestamp - lastPriceUpdate > 1 hours, 'Must wait at least 1 hour after updating price'); + taxLastpaid = block.timestamp; + + (bool sent,) = payable(Treasury).call{value: msg.value}(''); + require(sent, 'Failed to send'); + } + + function claimLeadership() external payable { + require(block.timestamp - taxLastpaid > 1 weeks, 'Leadership cannot be claimed'); + Leader = msg.sender; + } + + + function setSequencerAddress(address newSequencer) external { + require(msg.sender == Leader, 'Caller must be the Leader'); + SequencerAddress = newSequencer; + } + + + function isAllowed(address proposer) external view override returns (bool) { + return proposer == SequencerAddress; + } +} diff --git a/src/HighScoreModule.sol b/src/HighScoreModule.sol new file mode 100644 index 0000000..6299dd7 --- /dev/null +++ b/src/HighScoreModule.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: UNLICENSED + + +/* + + !!! DO NOT USE THIS CONTRACT IN PRODUCTION !!! + + This is exists purely as a thought experiment. No tests have been written, + so actual functionality may be different than what is advertised. It might + not even compile, for all I know + + + HIGH SCORE + + - The Leader, at any point, may choose the SequencerAddress + - Leadership is determined as follows: + - At the start, any address may claim the Leadership for free + - Once a Leader exists, another addres can claim the Leadership by posting + any amount of ETH. This ETH goes to the old Leader, and the amount becomes + the HighScore. + - All subsequent Leadership changes require an address to post an ETH amount + that is higher than the current HighScore +*/ + + +pragma solidity 0.8.25; + +import {IsAllowed} from "src/interfaces/IsAllowed.sol"; + + +contract HighScoreModule is IsAllowed { + address public Leader; + address public SequencerAddress; + uint256 public HighScore = 0; + + function claimLeadership() external payable { + if (Leader != address(0)) { + require(msg.value > HighScore, 'Insufficient HighScore Payment'); + (bool sent,) = payable(Leader).call{value: msg.value}(''); + require(sent, 'Failed to send'); + } + Leader = msg.sender; + } + + function setSequencerAddress(address newSequencer) external { + require(msg.sender == Leader, 'Caller must be the Leader'); + SequencerAddress = newSequencer; + } + + + function isAllowed(address proposer) external view override returns (bool) { + return proposer == SequencerAddress; + } +} diff --git a/src/KingOfTheHillModule.sol b/src/KingOfTheHillModule.sol new file mode 100644 index 0000000..84b3755 --- /dev/null +++ b/src/KingOfTheHillModule.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: UNLICENSED + + + + +/* + + !!! DO NOT USE THIS CONTRACT IN PRODUCTION !!! + + This is exists purely as a thought experiment. No tests have been written, + so actual functionality may be different than what is advertised. It might + not even compile, for all I know + + + KING OF THE HILL + + - The King, at any point, may choose the SequencerAddress + - At any point, any address may claim the Kingship +*/ + + +pragma solidity 0.8.25; + +import {IsAllowed} from "src/interfaces/IsAllowed.sol"; + + +contract KingOfTheHillModule is IsAllowed { + address public King; + address public SequencerAddress; + + function claimKingship() external { + King = msg.sender; + } + + function setSequencerAddress(address newSequencer) external { + require(msg.sender == King, 'Caller must be the King'); + SequencerAddress = newSequencer; + } + + + function isAllowed(address proposer) external view override returns (bool) { + return proposer == SequencerAddress; + } +} diff --git a/src/LeaderElectionModule.sol b/src/LeaderElectionModule.sol new file mode 100644 index 0000000..dc1d178 --- /dev/null +++ b/src/LeaderElectionModule.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: UNLICENSED + + +/* + + !!! DO NOT USE THIS CONTRACT IN PRODUCTION !!! + + This is exists purely as a thought experiment. No tests have been written, + so actual functionality may be different than what is advertised. It might + not even compile, for all I know + + + LEADER ELECTION + + - The contract is instantiated with a pool of 7 Voters + - The Leader can choose the SequencerAddress at any time + - At any point, a majority vote can overthrow the Leader + - This will nullify the current SequencerAddress + - This will trigger a new leader election, in which a majority + vote must pick a new Leader + - Overthrowing the Leader will nullify the SequencerAddress, so no blocks + can be sequenced while a leader election is in progress + +*/ + + +pragma solidity 0.8.25; + +import {IsAllowed} from "src/interfaces/IsAllowed.sol"; + + +contract LeaderElectionModule is IsAllowed { + address public Leader; + address public SequencerAddress; + + address[7] public VoterList; + mapping(address => bool) public Voters; + mapping(address => bool) public overthrowVotes; + mapping(address => address) public newLeaderVotes; + + constructor(address[7] voters) { + VoterList = voters; + + for (uint8 v; v < 7; v++) { + Voters[voters[v]] = true; + } + + Leader = voters[0] + } + + function overthrowVote(bool overthrow) external public { + require(Voters[msg.sender], 'Only Voters can vote'); + require(Leader != address(0), 'No Leader to overthrow'); + + overthrowVotes[msg.sender] = overthrow; + + if (totalOverthrowVotes() > 3) { + _overthrow(); + } + } + + + function newLeaderVote(address newLeader) external public { + require(Voters[msg.sender], 'Only Voters can vote'); + require(Leader == address(0), 'Must overthrow current Leader'); + newLeaderVotes[msg.sender] = newLeader; + + if (countVotes(newLeader) > 3) { + _setLeader(newLeader); + } + } + + + function totalOverthrowVotes() public view returns (uint8) { + uint8 overthrowCount; + for (uint8 v; v < 7; v++) { + if (overthrowVotes[VoterList[v]]) { + overthrowCount++; + } + } + + return overthrowCount; + } + + + function countVotes(address addr) public view returns (uint8) { + uint8 voteCount; + for (uint8 v; v < 7; v++) { + if (newLeaderVotes[VoterList[v]] == addr) { + voteCount++; + } + } + + return voteCount; + } + + + + function _setLeader(newLeader) private { + Leader = newLeader; + for (uint8 v; v < 7; v++) { + newLeaderVotes[VoterList[v]] = address(0); + } + } + + function _overthrow() private { + SequencerAddress = address(0); + Leader = address(0); + for (uint8 v; v < 7; v++) { + overthrowVotes[VoterList[v]] = false; + } + } + + + + + function setSequencerAddress(address newSequencer) external { + require(msg.sender == Leader, 'Caller must be the Leader'); + SequencerAddress = newSequencer; + } + + + function isAllowed(address proposer) external view override returns (bool) { + return proposer == SequencerAddress; + } +} diff --git a/src/NuclearWarModule.sol b/src/NuclearWarModule.sol new file mode 100644 index 0000000..47f79a9 --- /dev/null +++ b/src/NuclearWarModule.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: UNLICENSED + + +/* + + !!! DO NOT USE THIS CONTRACT IN PRODUCTION !!! + + This is exists purely as a thought experiment. No tests have been written, + so actual functionality may be different than what is advertised. It might + not even compile, for all I know + + + NUCLEAR WAR + + - Nine Leaders are chosen at instantiation + - All Leaders act as Sequencer addresses + - At any point, a Leader can nuke another Leader, removing them from the + Leader list. + - Completing a nuke operation takes 12 minutes, during which time the target + Leader can retaliate. + - If all Leaders are eliminated, there will be no valid proposer for isAllowed, + and no one will be able to sequence. +*/ + + +pragma solidity 0.8.25; + +import {IsAllowed} from "src/interfaces/IsAllowed.sol"; + + +contract HighScoreModule is IsAllowed { + mapping(address => bool) public LeaderList; + + enum MissleTargetStatus { + Dormant, + Fired, + Completed + } + + struct NuclearMissle { + address target; + MissleTargetStatus status; + uint256 firedAt; + } + + uint8 missleCount; + + mapping(uint256 => NuclearMissle) public Missles; + + address public SequencerAddress; + + constructor(address[9] leaders) { + for (address l; l < 9; l++) { + LeaderList[leaders[l]] = true; + } + } + + + function fireMissle(address target) external public returns (uint8) { + require(LeaderList[msg.sender], 'Only a Leader can fire a nuke'); + + uint8 missleId = missleCount + Missles[missleId].status = MissleTargetStatus.Fired; + Missles[missleId].target = target; + Missles[missleId].firedAt = block.timestamp; + + missleCount++; + + return missleId; + } + + function completeMissleLaunch(uint8 missleId) external public { + require(NuclearMissle[missleId].status == Fired, 'Can only complete a Fired missle'); + require(block.timestamp - NuclearMissle[missleId].timestamp >= 12 minutes, 'Must wait 12 minutes to complete missle launch'); + NuclearMissle[missleId].status = MissleTargetStatus.Completed; + LeaderList[NuclearMissle[missleId].target] = false; + } + + + function isAllowed(address proposer) external view override returns (bool) { + return proposer == LeaderList[msg.sender]; + } +} diff --git a/src/PirateGame.sol b/src/PirateGame.sol new file mode 100644 index 0000000..89bdded --- /dev/null +++ b/src/PirateGame.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: UNLICENSED + + +/* + + !!! DO NOT USE THIS CONTRACT IN PRODUCTION !!! + + This is exists purely as a thought experiment. No tests have been written, + so actual functionality may be different than what is advertised. It might + not even compile, for all I know + + + PIRATE GAME + + - The game starts with five Pirates, one of whom is the captain + - The Captain can propose the SequencerAddress at any time + - After a proposal, the Pirates vote + - If a proposal does not get a majority vote, the Captain is thrown + overboard, and the second in command becomes Captain + - Yays have the tie breaking vote + - Pirates can throw the Captain overboard with a majority vote at any time + +*/ + + +pragma solidity 0.8.25; + +import {IsAllowed} from "src/interfaces/IsAllowed.sol"; + + +contract PirateGameModule is IsAllowed { + enum Status { + Proposed, + Accepted, + Rejected + } + + enum VoteValue { + NoVote, + Yay, + Nay + } + + struct Proposal { + address sequencer; + Status status; + } + + Proposal public SequencerAddressProposal; + + address[5] public PirateList; + mapping(address => bool) public Pirates; + mapping(address => VoteValue) public Votes; + + constructor(address[5] pirates) { + for (uint8 p; p < 5; p++) { + Pirates[pirates[v]] = true; + } + PirateList = pirates; + SequencerAddressProposal.status = Status.Accepted; + } + + + function Captain() public view returns (address) { + return PirateList[PirateList.length - 1]; + } + + function propose(address sequencer) external { + require(msg.sender == Captain(), 'Only the Captain can propose'); + require( + proposed.status == Status.Accepted || proposed.status == Status.Rejected, + 'Another proposal cannot be made' + ); + + proposed.sequencer = sequencer; + proposed.status = Status.Proposed; + + Votes[msg.sender] = VoteValue.Yay; + } + + function vote(bool v) external { + require(PirateList[msg.sender], 'Only active Pirates can vote'); + + if (v) Votes[msg.sender] = VoteValue.Yay; + else Votes[msg.sender] = VoteValue.Nay; + + if (countVotes(VoteValue.Yay) >= PirateList.length / 2) _passVote() + else if (countVotes(VoteValue.Nay) > PirateList.length / 2) _rejectVote(); + } + + function countVotes(VoteValue val) public view returns (uint8) { + uint8 voteCount; + for (uint8 v; v < PirateList.length; v++) { + if (Votes[PirateList[v]] == val) { + voteCount++; + } + } + + return voteCount; + } + + function _passVote() private { + SequencerAddressProposal.status = Status.Accepted; + _nullifyVotes(); + } + + function _rejectVote() private { + _nullifyVotes(); + _throwCaptainOverboard(); + SequencerAddressProposal.status = Status.Rejected; + SequencerAddressProposal.sequencer = address(0); + } + + function _nullifyVotes() private { + for (uint8 v; v < PirateList.length; v++) { + Votes[PirateList[v]] = VoteValue.NoVote + } + } + + function _throwCaptainOverboard() private { + address formerCaptain = PirateList.pop(); + Pirates[formerCaptain] = false; + } + + function isAllowed(address proposer) external view override returns (bool) { + return proposer == SequencerAddressProposal.sequencer && SequencerAddressProposal.status == Status.Accepted; + } +} diff --git a/src/RandomModule.sol b/src/RandomModule.sol new file mode 100644 index 0000000..6f80b0f --- /dev/null +++ b/src/RandomModule.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: UNLICENSED + + +/* + + !!! DO NOT USE THIS CONTRACT IN PRODUCTION !!! + + This is exists purely as a thought experiment. No tests have been written, + so actual functionality may be different than what is advertised. It might + not even compile, for all I know + + + RANDOM + + - A list of potential sequencers is defined at contract instantiation + - All sequencers must post a bond + - Every block, a random participant from the list is chosen + - If a block isn't correctly sequenced, the sequencer responsible for that block gets slashed + - Governance regarding who is in the sequencer list is TBD + - How can one tell if a block was correctly sequenced? +*/ + + +pragma solidity 0.8.25; + +import {IsAllowed} from "src/interfaces/IsAllowed.sol"; + + +contract RandomModule is IsAllowed { + uint256[] public slots; + uint256 public TOTAL_SLOTS; + uint256 public BOND_AMOUNT = 1 ether; + mapping(address => uint256) public bondBalance; + mapping(uint256 => bool) public slashed; + + constructor(uint256[] sequencerList) { + TOTAL_SLOTS = sequencerList.length; + slots = sequencerList; + } + + function isAllowed(address proposer) external view override returns (bool) { + address sequencer = sequencerForBlock(block.number); + return ( + proposer == sequencer + && bondBalance[sequencer] >= BOND_AMOUNT + ); + } + + + function sequencerForBlock(uint256 blockNumber) external view returns (address) { + uint256 slot = uint256(blockhash(blockNumber - 1)) % TOTAL_SLOTS; + return sequencerList[slot]; + } + + + function postBond() external payable { + bondBalance[msg.sender] += msg.value; + } + + function slash(uint256 blockNumber) external { + if (wasBlockSequenced(blockNumber) && !slashed[blockNumber]) { + address sequencer = sequencerForBlock(blockNumber); + bondBalance[sequencer] -= BOND_AMOUNT; + payable(msg.sender).call{ value: BOND_AMOUNT, gas: 30_000 }(new bytes(0)); + slashed[blockNumber] = true; + } + } + + function wasBlockSequenced(uint256 blockNumber) external view returns (bool) { + // How can you tell if a block was correctly sequenced? + return true; + } +} diff --git a/src/UltimatumGameModule.sol b/src/UltimatumGameModule.sol new file mode 100644 index 0000000..0790777 --- /dev/null +++ b/src/UltimatumGameModule.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: UNLICENSED + + +/* + + !!! DO NOT USE THIS CONTRACT IN PRODUCTION !!! + + This is exists purely as a thought experiment. No tests have been written, + so actual functionality may be different than what is advertised. It might + not even compile, for all I know + + + ULTIMATUM GAME + + - A Proposer and a Responder are chosen at instantiation + - At any point, the Proposer may propose a SequencerAddress + - The Responder has 1 hour to accept the proposal + - If the proposal is not accepted within 1 hour, it may no longer be accepted, + and my be rejected by anyone. + - If a proposal fails, then blocks can no longer be sequenced + +*/ + + +pragma solidity 0.8.25; + +import {IsAllowed} from "src/interfaces/IsAllowed.sol"; + + +contract UltimatumGameModule is IsAllowed { + address public Proposer; + address public Responder; + + + enum Status { + Proposed, + Accepted, + Rejected + } + + struct Proposal { + address sequencer; + uint256 timestamp; + Status status; + } + + Proposal public SequencerAddressProposal; + + constructor(address proposer, address responder) { + Proposer = proposer; + Responder = responder; + + SequencerAddressProposal.status = Status.Accepted; + } + + + function propose(address sequencer) external { + require(msg.sender == Proposer, 'Only the Proposer can propose'); + require(SequencerAddressProposal.status == Status.Accepted, 'A proposal cannot be made'); + + SequencerAddressProposal.sequencer = sequencer; + SequencerAddressProposal.timestamp = block.timestamp; + SequencerAddressProposal.status = Status.Proposed; + } + + function accept() external { + require(msg.sender == Responder, 'Only the Responder can accept the proposal'); + require(block.timestamp - SequencerAddressProposal.timestamp <= 1 hours, 'The proposal can no longer be accepted'); + require(SequencerAddressProposal.status == Proposed, 'The proposal cannot be accepted'); + + SequencerAddressProposal.status = Status.Accepted; + } + + function reject() external { + require(SequencerAddressProposal.status == Status.Proposed, 'The proposal cannot be rejected'); + if (block.timestamp - SequencerAddressProposal.timestamp < 1 hours) { + require(msg.sender == Responder, 'Only the Responder can reject the proposal'); + } + + SequencerAddressProposal.status = Status.Rejected; + } + + + function isAllowed(address proposer) external view override returns (bool) { + return proposer == SequencerAddressProposal.sequencer && SequencerAddressProposal.status == Status.Accepted; + } +}