블록체인을 활용한 겜블링 DApp
→ 외부 규제 및 무조건적인 신뢰 불필요
address public owner;
address payable[] public players;
uint256 public lotteryId;
mapping(uint256 => address) public lotteryHistory;
constructor(){
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
function getPlayers() public view returns (address payable[] memory){
return players;
}
function enter() public payable{
require(msg.value >= .01 ether,"msg.value should be greater than or equal to 0.01 ether");
players.push(payable(msg.sender));
}
// pickWinner()는 아무나 호출하면 안되기에 modifier로 onlyOwner만 호출할 수 있도록 설정하였다.
function pickWinner() public onlyOwner{
uint256 index = getRandomNumber() % players.length;
lotteryHistory[lotteryId] = players[index];
lotteryId++;
(bool success, ) = players[index].call{value: address(this).balance}("");
require(success, "Failed to send Ether");
players = new address payable[](0);
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
}
Re-Entrancy Attack
을 당할 수 있다.function getRandomNumber() public view returns (uint256){
return uint256(keccak256(abi.encodePacked(owner, block.timestamp)));
}
function getRandomNumberV2() public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.difficulty, block.timestamp, players)));
}
→ 모두 합쳐 해시된 값을 uint256으로 바꿔준다.
function getRandomNumberV3() public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(blockhash(block.number -1 ), block.timestamp)));
}
→ 모두 합쳐 해시된 값을 uint256으로 바꿔준다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract Lottery {
address public owner;
// 플레이어들은 이더를 들고 참여한다.
address payable[] public players;
uint256 public lotteryId;
mapping(uint256 => address) public lotteryHistory;
constructor(){
// 해당 컨트랙트를 배포하는 사람이 owner이 된다.
owner = msg.sender;
}
// 해당 컨트랙트가 가지고 있는 총 ETH balance의 양을 반환한다.
function getBalance() public view returns (uint256) {
// address(this) : 컨트랙트 자체를 뜻한다.
return address(this).balance;
}
// 참여한 모든 플레이어의 주소 배열을 리턴하는 함수
// players는 address payable 타입의 배열이므로 Return 시 address payable[] memory여야 한다.
function getPlayers() public view returns (address payable[] memory){
// memory: players 값은 storage에 저장되어 있는 값이므로 해당 값을 읽어 return 하고자 할 경우엔 memory 타입이어야 한다.
// storage에 있는 내용을 memory에 복사를해서 리턴해준다.
return players;
}
// 플레이어들이 이더를 들고 참여할 때 호출하는 함수
// - 그래서 payable 타입의 함수
// - 0.01이더씩 들고오는 것으로 구현
function enter() public payable{
require(msg.value >= .01 ether,"msg.value should be greater than or equal to 0.01 ether");
// payable 타입으로 명시적 변환하여 대입한다.
players.push(payable(msg.sender));
}
// abi.encodePacked(owner, block.timestamp) : owner와 block.timestamp 각가을 bytes로 converting한 값을 concat한 값
// abi.encode() : converting 시 부족한 값을 0으로 모두 채워놓는다.
// abi.encodePacked() : 실제 차지하는 공간에 대해서만 채워준다.
// concat한 값을 keccak256 해시 알고리즘으로 해시한 값
// 해시한 값을 uint256으로 converting한 값
// => 랜덤한 값
function getRandomNumber() public view returns (uint256){
return uint256(keccak256(abi.encodePacked(owner, block.timestamp)));
}
function getRandomNumberV2() public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.difficulty, block.timestamp, players)));
}
function getRandomNumberV3() public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(blockhash(block.number -1 ), block.timestamp)));
}
// pickWinner()는 아무나 호출하면 안되기에 modifier로 onlyOwner만 호출할 수 있도록 설정하였다.
function pickWinner() public onlyOwner{
// 랜덤값을 참여한 players들의 수로 나눈 나머지 -> 참여한 players 중에 랜덤하게 뽑는다는 의미
uint256 index = getRandomNumber() % players.length;
lotteryHistory[lotteryId] = players[index];
lotteryId++;
// 랜덤하게 뽑힌 player에게 컨트랙트의 모든 ETH를 전송한다.
// 요즘 가장 많이 사용하는 이더 전송 구문 = 안전하다
(bool success, ) = players[index].call{value: address(this).balance}("");
require(success, "Failed to send Ether");
// 다음 회차를 위해 players 배열을 초기화한다. (리셋시켜주는 구문 : 해당 배열에 대해 length를 0으로 바꿔주겠다는 의미)
players = new address payable[](0);
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
}
[vm]from: 0x5B3...eddC4to: Lottery.(constructor)value: 0 weidata: 0x608...10033logs: 0hash: 0x729...4881f
status true Transaction mined and execution succeed
transaction hash 0x729e48ee424aebec54e7fad8ed1a913d8b474352273e60573102b05f49e4881f
from 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
to Lottery.(constructor)
gas 1065396 gas
transaction cost 926431 gas
execution cost 926431 gas
input 0x608...10033
decoded input {}
decoded output -
logs []
val 0 wei
0x5B3..가 배포한다(배포자 == Onwer)
[Output]
0
[Output]
address[]
첫번째 참여자(0xAb8..)가 0.01 ETH를 내고 게임에 참여한다. (잔고 99.98ETH = 100ETH - 0.01ETH)
[Output]
10000000000000000
[Output]
address[]: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
두번째 참여자(0x4B2..)가 0.01 ETH를 내고 게임에 참여한다. (잔고 99.98ETH = 100ETH - 0.01ETH)
[Output]
20000000000000000
[Output]
address[]:
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,
0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db
세번째 참여자(0x787..)가 0.01 ETH를 내고 게임에 참여한다. (잔고 99.98ETH = 100ETH - 0.01ETH)
[Output]
30000000000000000
[Output]
address[]:
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,
0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db,
0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB
네번째 참여자(0x617..)가 0.01 ETH를 내고 게임에 참여한다. (잔고 99.98ETH = 100ETH - 0.01ETH)
[Output]
40000000000000000
[Output]
address[]:
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,
0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db,
0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB,
0x617F2E2fD72FD9D5503197092aC168c91465E7f2
Owner(0x5B3..)가 승리자를 뽑아내는 함수를 호출한다.
[Input]
0
[Output]
address : 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
payable(to).transfer(1 ether);
bool success = payable(to).send(1 ether);
require(success, "Failed to send ETH");
(bool success, ) = payable(to).call{value: 1 ether}("");
require(success, "Failed to send ETH");
(bool success, bytes memory data) = payable(to).call{value: value}(abi.encodeWithSignature("foo(string, uint256)", "call foo", 123));
uint256 public commitCloses;
uint256 public revealCloses;
uint256 public constant DURATION = 4;
address public winner;
bytes32 seed;
mapping (address => bytes32) public commitments;
constructor() {
commitCloses = block.number + DURATION;
revealCloses = commitCloses + DURATION;
}
// 참여자는 외부에서 secret 값을 생성한 후 해시하여 commit 값 생성 후, 0.01 이상의 ETH와 함께 commit 값을 등록
function enter(bytes32 commitment) public payable {
require(msg.value >= .01 ether, "msg.value should be greater than or equal to 0.01 ether");
// commit이 종료되는 블록 넘버 이전까지 참여 가능
require(block.number < commitCloses, "commit duration is over");
// 각 참여자마다의 commit 값 저장
commitments[msg.sender] = commitment;
}
function createCommitment(uint256 secret) public view returns (bytes32){
// 함수를 콜한 주소와 입력한 secret 값을 해시한 값을 리턴한다.
return keccak256(abi.encodePacked(msg.sender, secret));
}
// commit 시 참여했던 자가 그 당시 사용한 secret 값을 공개하며 이를 이용해 랜덤값 생성
function reveal(uint256 secret) public {
// commit 기간 종료 후부터 reveal 기간 종료 전까지만 가능하다.
require(block.number >= commitCloses, "commit duration is not closed yet");
require(block.number < revealCloses, "reveal duration is already closed");
require(!isAlreadyRevealed(), "You already revealed");
// enter() 에서 입력한 secret 값에 대해 해시한 값이 commit시 등록한 해시값과 일치하는지 확인
bytes32 commit = createCommitment(secret);
require(commit == commitments[msg.sender], "commit not matches");
// 일치한다면 이를 seed 값에 이어서 해시한다.
seed = keccak256(abi.encodePacked(seed, secret));
players.push(msg.sender);
}
function isAlreadyRevealed() public view returns (bool){
for (uint256 i=0;i < players.length; i++){
if (msg.sender == players[i]){
return true;
}
}
return false;
}
// reveal 단계에서 결정된 랜덤값인 seed를 통해 참여한 players 중 winner 선정
// 충분한 참여 기간이 지난 후에 호출이 가능하므로 onlyOwner일 필요가 없다.
function pickWinner() public {
require(block.number >= revealCloses, "Not yet to pick winner");
// winner가 세팅되기 전인지 미리 확인한다.
require(winner == address(0), "winner is already set");
// 랜덤한 값 % 참여한 플레이어의 수 = winner 선정 완료
winner = players[uint256(seed) % players.length];
// winner 기록
lotteryHistory[lotteryId] = winner;
lotteryId++;
}
// 함수 호출자가 winner일 경우 컨트랙트에 쌓인 모든 ETH를 획득한다.
function withdrawPrize() public {
require(msg.sender == winner, "You're not the winner");
// 다음 회차를 위해 관련 데이터 초기화 및 commit, reveal 기간 재설정
delete winner;
for (uint256 i =0;i< players.length; i++){
// 각각의 사용자들이 커밋했던 값들을 모두 지워준다.
delete commitments[players[i]];
}
delete players;
delete seed;
// re-entrancy attack 방지를 위해 call() 호출 전에 상태값 변경
commitCloses = block.number + DURATION;
revealCloses = commitCloses + DURATION;
//msg.sender는 이더를 받아야하기 때문에 payable()로 치환한다.
(bool success, ) = payable(msg.sender).call{value: address(this).balance}("");
require(success, "Failed to send Ether to winner");
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract CommitRevealLottery{
uint256 public commitCloses;
uint256 public revealCloses;
uint256 public constant DURATION =4;
uint256 public lotteryId;
address[] public players;
address public winner;
bytes32 seed;
mapping (address =>bytes32) public commitments;
mapping (uint256 => address) public lotteryHistory;
constructor() {
commitCloses = block.number + DURATION;
revealCloses = commitCloses + DURATION;
}
// 참여자는 외부에서 secret 값을 해시하여 commit 값 생성 후, 0.01 이상의 ETH와 함께 commit 값을 등록
function enter(bytes32 commitment) public payable {
require(msg.value >= .01 ether, "msg.value should be greater than or equal to 0.01 ether");
// commit이 종료되는 블록 넘버 이전까지 참여 가능
require(block.number < commitCloses, "commit duration is over");
// 각 참여자마다의 commit 값 저장
commitments[msg.sender] = commitment;
}
// 이 컨트랙트에서의 commit 값 생성
function createCommitment(uint256 secret) public view returns (bytes32){
// 함수를 콜한 주소와 입력한 secret 값을 해시한 값을 리턴한다.
return keccak256(abi.encodePacked(msg.sender, secret));
}
// commit 시 참여했던 자가 그 당시 사용한 secret 값을 공개하며 이를 이용해 랜덤값 생성
function reveal(uint256 secret) public {
// commit 기간 종료 후부터 reveal 기간 종료 전까지만 가능하다.
require(block.number >= commitCloses, "commit duration is not closed yet");
require(block.number < revealCloses, "reveal duration is already closed");
require(!isAlreadyRevealed(), "You already revealed");
// enter() 에서 입력한 secret 값에 대해 해시한 값이 commit시 등록한 해시값과 일치하는지 확인
bytes32 commit = createCommitment(secret);
require(commit == commitments[msg.sender], "commit not matches");
// 일치한다면 이를 seed 값에 이어서 해시한다.
seed = keccak256(abi.encodePacked(seed, secret));
players.push(msg.sender);
}
function isAlreadyRevealed() public view returns (bool){
for (uint256 i=0;i < players.length; i++){
if (msg.sender == players[i]){
return true;
}
}
return false;
}
// reveal 단계에서 결정된 랜덤값인 seed를 통해 참여한 players 중 winner 선정
// 충분한 참여 기간이 지난 후에 호출이 가능하므로 onlyOwner일 필요가 없다.
function pickWinner() public {
require(block.number >= revealCloses, "Not yet to pick winner");
// winner가 세팅되기 전인지 미리 확인한다.
require(winner == address(0), "winner is already set");
// 랜덤한 값 % 참여한 플레이어의 수 = winner 선정 완료
winner = players[uint256(seed) % players.length];
// winner 기록
lotteryHistory[lotteryId] = winner;
lotteryId++;
}
// 함수 호출자가 winner일 경우 컨트랙트에 쌓인 모든 ETH를 획득한다.
function withdrawPrize() public {
require(msg.sender == winner, "You're not the winner");
// 다음 회차를 위해 관련 데이터 초기화 및 commit, reveal 기간 재설정
delete winner;
for (uint256 i =0;i< players.length; i++){
// 각각의 사용자들이 커밋했던 값들을 모두 지워준다.
delete commitments[players[i]];
}
delete players;
delete seed;
// re-entrancy attack 방지를 위해 call() 호출 전에 상태값 변경
commitCloses = block.number + DURATION;
revealCloses = commitCloses + DURATION;
//msg.sender는 이더를 받아야하기 때문에 payable()로 치환한다.
(bool success, ) = payable(msg.sender).call{value: address(this).balance}("");
require(success, "Failed to send Ether to winner");
}
}
[vm]from: 0x5B3...eddC4to: CommitRevealLottery.(constructor)value: 0 weidata: 0x608...10033logs: 0hash: 0x84a...2a00f
status true Transaction mined and execution succeed
transaction hash 0x84a186685d8e34522d0c28b347cf890609653d961f29842fb95076d53c52a00f
from 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
to CommitRevealLottery.(constructor)
gas 1361561 gas
transaction cost 1183966 gas
execution cost 1183966 gas
input 0x608...10033
decoded input {}
decoded output -
logs []
val 0 wei
Owner(0x5B3..)이 컨트랙트를 배포한다.
첫번째 참가자(0xAb8…)가 createCommitment함수에 자신만의 시크릿 값을 넣고 랜덤한 값을 발급받는다.
[Input]
secret : 12345
[Output]
0xfc8a9c938ba50da41c81b2a941a31d837de63f174078ec4fbeaa96793309a1e9
첫번째 참가자가 게임 참가를 위해 랜덤한 값과 0.01 이더를 enter 함수에 넣는다.
[Input]
0xfc8a9c938ba50da41c81b2a941a31d837de63f174078ec4fbeaa96793309a1e9
[Output]
10000000000000000
[Input]
secret : 12345
[Input]
0
[Output]
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
[Input]
0xa29ed35d868e5623be6a4a4d27aa2f9bb1d1dcc4dbcacf8818629050980a35d9
두번째 참가자(0x4B2…)가 createCommitment함수에 자신만의 시크릿 값을 넣고 랜덤한 값을 발급받는다.
[Input]
secret : 12346
[Output]
0x63c2e622aa612614d01119aa186104624ab8110e28fe5e1c794ad9710292a3ae
두번째 참가자가 게임 참가를 위해 랜덤한 값과 0.01 이더를 enter 함수에 넣는다.
[Input]
0x63c2e622aa612614d01119aa186104624ab8110e28fe5e1c794ad9710292a3ae
[Output]
20000000000000000
[Input]
secret : 12346
[Input]
0
[Output]
0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db
[Input]
0xb3fd9c957e204c197772c08ee1da810b743da485cf98599c5b2d43a59912917b
세번째 참가자(0x787…)가 createCommitment함수에 자신만의 시크릿 값을 넣고 랜덤한 값을 발급받는다.
[Input]
secret : 12347
[Output]
0xa71cdc84e102623c30a61ef122d25b1830bbdac6a3368fb8df711266062873e1
세번째 참가자가 게임 참가를 위해 랜덤한 값과 0.01 이더를 enter 함수에 넣는다.
[Input]
0xa71cdc84e102623c30a61ef122d25b1830bbdac6a3368fb8df711266062873e1
[Output]
30000000000000000
[Input]
secret : 12347
[Input]
2
[Output]
0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB
[Input]
0x6c770330d3c0e12a6441195e5800cd814239a3169d526c0d20d24c44a8429d10
[Output]
0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db
[Output]
1
[Intput]
0
[Output]
0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db
The experience transformed blackjack for me from a casual pastime into a deeply engaging mental challenge. The game became not just about winning or losing, but about how well I could stick to my strategy and outsmart the dealer. When you learn the game, every hand becomes an opportunity to test your https://betzinocasino-fr1.com/ knowledge, adapt to changing conditions, and make the most out of every situation. It’s an addictive feeling, knowing that you're no longer at the mercy of luck alone but have the power to shape your fate through skill and strategy.
I've been busier than a squirrel during harvest time, I tell you. From conquering virtual domains to unraveling the complexities of financial markets, it's been quite the journey. These games have acted as my sanctuary, providing both challenge and excitement amidst the mundane. But let's not overlook the pragmatic side of things. I've delved into the realm of investments, aiming to secure my financial future. Can't sugarcoat it – it's been a пинап казино rollercoaster ride. With its twists and turns, peaks and valleys, but I'm navigating through with steadfast determination. And when those profits begin to flow, it's like uncovering a hidden treasure trove. Here's to more adventures on this platform, where the games are thrilling, and the investments ripe for the taking!