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": {
"@openzeppelin/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);
}
}
"
},
"@openzeppelin/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;
}
}
"
},
"@openzeppelin/contracts/utils/structs/EnumerableSet.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
"
},
"contracts/Raffle.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/// @title Raffle contract to start a raffle with as many users as possible
/// @author @Bullrich
/// @notice Only the deployer of the contract can finish the raffle
/// @custom:security-contact info+security@rafflchain.com
contract Raffle is Ownable {
using EnumerableSet for EnumerableSet.AddressSet;
/// Different type of the bundles. Used for specifying a purchase
enum BundleSize {
Small,
Medium,
Large
}
/// Triggered when user wants to interact with a finished raffle
error RaffleOver();
/// Triggered when the owner is trying to participate in its own raffle
error OwnerCannotParticipate();
/// Triggered when the purchase number or type is invalid
error InvalidPurchase();
/// Triggered on lack of funds for the selected bundle
error InsufficientFunds();
/// User tried to refer an address that is himself or someone who is not playing
error InvalidReferral(string);
/// User tried to claim the free ticket more than one
error FreeTicketClaimed();
/// There was a problem while finishing the Raffle
error ErrorFinishing(string);
/// There was a problem while transfering funds on the finished raffle
error TransferFailed(uint, address);
/// Set with all the players participating. Each user has tickets
EnumerableSet.AddressSet private players;
/// Tickets each player owns
mapping(address => uint) public tickets;
/// Emitted when the raffle is over
event WinnerPicked(address winner);
/// Emitted when a user is referred
event Referred(address referral);
/// Timestamp of when the raffle ends
uint public immutable raffleEndDate;
/// The fixed prize that will be given to the winner
/// @dev This is if that amount gets reached, if not the pot is split in half
uint public immutable fixedPrize;
/// Address of the winner
/// @dev this value is set up only after the raffle end
address public winner;
/// Container of ticket information.
struct Bundle {
uint amount;
uint price;
}
/// Size of the small bundle
uint16 public constant SMALL_BUNDLE_AMOUNT = 45;
/// Price of the small bundle
uint public immutable smallBundlePrice;
/// Size of the medium bundle
uint16 public constant MEDIUM_BUNDLE_AMOUNT = 200;
/// Price of the medium bundle
/// @notice the final price should be discounted than buying the same amount of small bundles
uint public immutable mediumBundlePrice;
/// Size of the large bundle
uint16 public constant LARGE_BUNDLE_AMOUNT = 660;
/// Prize of the large bundle
/// @notice the final price should be discounted than buying the same amount of small bundles
uint public immutable largeBundlePrice;
/// @param ticketPrice Price of each ticket (without the decimals)
/// @param daysToEndDate Duration of the Raffle (in days)
/// @param _fixedPrize the prize pool that we are aiming to reach. Exceding pot will go to charity
constructor(uint ticketPrice, uint8 daysToEndDate, uint _fixedPrize) Ownable(msg.sender) {
raffleEndDate = block.timestamp + (daysToEndDate * 1 days);
fixedPrize = _fixedPrize;
smallBundlePrice = ticketPrice;
mediumBundlePrice = ticketPrice * 3;
largeBundlePrice = ticketPrice * 5;
}
/// Utility method used to buy any given amount of tickets
/// @param sizeOfBundle the number of tickets that will be purchased
/// @param priceOfBundle the amount to pay for the bundle
function buyCollectionOfTickets(uint sizeOfBundle, uint priceOfBundle) private returns (uint) {
if (block.timestamp > raffleEndDate) revert RaffleOver();
if (!(sizeOfBundle > 0 && priceOfBundle > 0)) revert InvalidPurchase();
if (msg.sender == owner()) revert OwnerCannotParticipate();
if (msg.value < priceOfBundle) revert InsufficientFunds();
players.add(msg.sender);
uint playerTickets = tickets[msg.sender];
tickets[msg.sender] = playerTickets + sizeOfBundle;
return sizeOfBundle;
}
/// Gives a ticket to a user who refered this player
/// @param referral address of the user to give the referal bonus
/// @dev the referring user must have own a ticket, proving that they are real accounts
function addReferral(address referral) private {
if (referral == msg.sender) revert InvalidReferral("Referring themselves");
if (!players.contains(referral)) revert InvalidReferral("Not a player");
tickets[referral] += 1;
emit Referred(referral);
}
/// Buy a bundle of tickets and refer a user
/// @param size of the bundle
/// @param referral Address to give a referral ticket on purchaser
function buyTicketBundleWithReferral(BundleSize size, address referral) external payable returns (uint) {
uint receipt = buyTicketBundle(size);
addReferral(referral);
return receipt;
}
/// Buy a bundle of tickets
/// @param size of the bundle
function buyTicketBundle(BundleSize size) public payable returns (uint) {
if (size == BundleSize.Small) {
return buyCollectionOfTickets(SMALL_BUNDLE_AMOUNT, smallBundlePrice);
} else if (size == BundleSize.Medium) {
return buyCollectionOfTickets(MEDIUM_BUNDLE_AMOUNT, mediumBundlePrice);
} else if (size == BundleSize.Large) {
return buyCollectionOfTickets(LARGE_BUNDLE_AMOUNT, largeBundlePrice);
} else {
revert InsufficientFunds();
}
}
/// Fallback function for when ethers is transfered randomly to this contract
receive() external payable {
if (msg.sender == owner()) revert OwnerCannotParticipate();
if (block.timestamp > raffleEndDate) revert RaffleOver();
if (msg.value >= largeBundlePrice) {
buyCollectionOfTickets(LARGE_BUNDLE_AMOUNT, msg.value);
} else if (msg.value >= mediumBundlePrice) {
buyCollectionOfTickets(MEDIUM_BUNDLE_AMOUNT, msg.value);
} else if (msg.value >= smallBundlePrice) {
buyCollectionOfTickets(SMALL_BUNDLE_AMOUNT, msg.value);
} else {
revert InsufficientFunds();
}
}
/// Returns all the available bundles sorted from smaller to bigger
function getBundles() external view returns (Bundle[] memory) {
Bundle[] memory bundles = new Bundle[](3);
bundles[0] = Bundle(SMALL_BUNDLE_AMOUNT, smallBundlePrice);
bundles[1] = Bundle(MEDIUM_BUNDLE_AMOUNT, mediumBundlePrice);
bundles[2] = Bundle(LARGE_BUNDLE_AMOUNT, largeBundlePrice);
return bundles;
}
/// User obtains a free ticket
/// @notice only the fist ticket is free
function getFreeTicket() external returns (uint) {
if (players.contains(msg.sender)) revert FreeTicketClaimed();
if (msg.sender == owner()) revert OwnerCannotParticipate();
players.add(msg.sender);
tickets[msg.sender] = 1;
return 1;
}
/// Calculate the total number of tickets
/// @notice Can only be invoked by the contract owner
function listSoldTickets() public view onlyOwner returns (uint256) {
uint ticketsSold = 0;
for (uint256 i = 0; i < players.length(); i++) {
ticketsSold += tickets[players.at(i)];
}
return ticketsSold;
}
/// Picks a random winner using a weighted algorithm
/// @notice the algorithm randomness can be predicted if triggered automatically, better to do it manually
function pickRandomWinner() private view returns (address) {
uint totalTickets = listSoldTickets();
if (totalTickets == 0) revert ErrorFinishing("No players");
// Generate a pseudo-random number based on block variables
uint randomNumber = uint(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, block.number))) %
totalTickets;
uint cumulativeSum = 0;
// Iterate over players to find the winner
for (uint i = 0; i < players.length(); i++) {
cumulativeSum += tickets[players.at(i)];
if (randomNumber < cumulativeSum) {
return players.at(i);
}
}
// This case should never occur if the function is implemented correctly
revert ErrorFinishing("Unknown");
}
/// See how the prize would be distributed between end users
/// @return prize that will go to the winner.
/// Usually it's s fixedPrize but if that amount is not reached, then it's half of the pot.
/// @return donation amount. It's 75% of the remaining pot.
/// @return commission that will go to the contract owner.
function prizeDistribution() public view returns (uint, uint, uint) {
uint prize = prizePool();
uint remainingPool = address(this).balance - prize;
uint donation = (remainingPool * 75) / 100;
uint commission = remainingPool - donation;
return (prize, donation, commission);
}
/// See what would be the prize pool with the current treasury
function prizePool() public view returns (uint) {
if (address(this).balance > fixedPrize) {
return fixedPrize;
}
return address(this).balance / 2;
}
/// Method used to finish a raffle
/// @param donationAddress Address of the charity that will receive the tokens
/// @notice Can only be called by the owner after the timestamp of the raffle has been reached
function finishRaffle(address payable donationAddress) external onlyOwner returns (address) {
if (block.timestamp < raffleEndDate) revert RaffleOver();
if (winner != address(0)) revert RaffleOver();
winner = pickRandomWinner();
emit WinnerPicked(winner);
// Divide into parts
(uint prize, uint donation, uint commission) = prizeDistribution();
// Send to the winner
(bool successWinner, ) = payable(winner).call{value: prize}("");
if (!successWinner) revert TransferFailed(prize, winner);
// Send to the charity address
(bool successDonation, ) = donationAddress.call{value: donation}("");
if (!successDonation) revert TransferFailed(donation, donationAddress);
// Get the commision
(bool successOwner, ) = payable(owner()).call{value: commission}("");
if (!successOwner) revert TransferFailed(commission, owner());
return winner;
}
}
"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 2000
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}
}}
Submitted on: 2025-10-24 11:32:06
Comments
Log in to comment.
No comments yet.