블록체인을 활용한 겜블링 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
Reflecting on my journey, I’ve come to appreciate blackjack for the strategic challenge it offers. It’s not just about luck—it’s about using https://1winph1.com.ph/ knowledge and skill to tilt the odds in your favor. With each game, I feel more confident and in control, and I look forward to continuing to refine my strategy.
Once, they were a name whispered in reverence among the gambling elite - a master of their craft, a legend in their own right. But then, disaster struck, and their world came crashing down in a torrent of defeat and despair. Now, they are but a shadow of their former self, a fallen champion seeking redemption in the unlikeliest of places.
Our story follows the journey of this former professional gambler https://betano-chile.click/ as they embark on a quest to reclaim their former glory. Armed with little more than determination and a burning desire to prove themselves, they set their sights on the world of online casinos, where fortunes are made and lost in the blink of an eye.