-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Mainnet] Hashbattle Gaming Contract #84
base: master
Are you sure you want to change the base?
Changes from 5 commits
c5c64bf
aef2843
bc2fabe
e4f1855
f96982c
6463f9b
b366150
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
| ||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 16 | ||
VisualStudioVersion = 16.0.31624.102 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBattle", "HashBattle\HashBattle.csproj", "{D711FA52-750E-481B-9BC5-2E07EBF58240}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashBattleTest", "HashBattleTest\HashBattleTest.csproj", "{2A7F2670-A17F-46E6-BF35-A2C55BC7DCB1}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{D711FA52-750E-481B-9BC5-2E07EBF58240}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{D711FA52-750E-481B-9BC5-2E07EBF58240}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{D711FA52-750E-481B-9BC5-2E07EBF58240}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{D711FA52-750E-481B-9BC5-2E07EBF58240}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{2A7F2670-A17F-46E6-BF35-A2C55BC7DCB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{2A7F2670-A17F-46E6-BF35-A2C55BC7DCB1}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{2A7F2670-A17F-46E6-BF35-A2C55BC7DCB1}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{2A7F2670-A17F-46E6-BF35-A2C55BC7DCB1}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {88A96B52-6F00-40C9-AA28-C3D79E3BC0DF} | ||
EndGlobalSection | ||
EndGlobal |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,299 @@ | ||||||
using Stratis.SmartContracts; | ||||||
using System; | ||||||
using System.Text; | ||||||
|
||||||
/// <summary> | ||||||
/// A Stratis smart contract for running a game battle where owner will start the battle and maximum 4 users can enter a battle | ||||||
/// </summary> | ||||||
public class Arena : SmartContract | ||||||
{ | ||||||
private void SetBattle(ulong battleId, BattleMain battle) | ||||||
{ | ||||||
State.SetStruct($"battle:{battleId}", battle); | ||||||
} | ||||||
public BattleMain GetBattle(ulong battleId) | ||||||
{ | ||||||
return State.GetStruct<BattleMain>($"battle:{battleId}"); | ||||||
} | ||||||
private void SetUser(ulong battleId, Address address, BattleUser user) | ||||||
{ | ||||||
State.SetStruct($"user:{battleId}:{address}", user); | ||||||
} | ||||||
public BattleUser GetUser(ulong battleId, Address address) | ||||||
{ | ||||||
return State.GetStruct<BattleUser>($"user:{battleId}:{address}"); | ||||||
} | ||||||
private void SetHighestScorer(ulong battleId, BattleHighestScorer highestScorer) | ||||||
{ | ||||||
State.SetStruct($"scorer:{battleId}", highestScorer); | ||||||
} | ||||||
public BattleHighestScorer GetHighestScorer(ulong battleId) | ||||||
{ | ||||||
return State.GetStruct<BattleHighestScorer>($"scorer:{battleId}"); | ||||||
} | ||||||
private void SetUserIndex(ulong battleId, uint userindex) | ||||||
{ | ||||||
State.SetUInt32($"user:{battleId}", userindex); | ||||||
} | ||||||
private uint GetUserIndex(ulong battleId) | ||||||
{ | ||||||
return State.GetUInt32($"user:{battleId}"); | ||||||
} | ||||||
private void SetScoreSubmittedCount(ulong battleId, uint scoresubmitcount) | ||||||
{ | ||||||
State.SetUInt32($"scoresubmit:{battleId}", scoresubmitcount); | ||||||
} | ||||||
private uint GetScoreSubmittedCount(ulong battleId) | ||||||
{ | ||||||
return State.GetUInt32($"scoresubmit:{battleId}"); | ||||||
} | ||||||
/// <summary> | ||||||
/// Set the address deploying the contract as battle owner | ||||||
/// </summary> | ||||||
public Address Owner | ||||||
{ | ||||||
get => State.GetAddress(nameof(Owner)); | ||||||
private set => State.SetAddress(nameof(Owner), value); | ||||||
} | ||||||
public Address PendingOwner | ||||||
{ | ||||||
get => State.GetAddress(nameof(PendingOwner)); | ||||||
private set => State.SetAddress(nameof(PendingOwner), value); | ||||||
} | ||||||
public uint MaxUsers | ||||||
{ | ||||||
get => State.GetUInt32(nameof(MaxUsers)); | ||||||
private set => State.SetUInt32(nameof(MaxUsers), value); | ||||||
} | ||||||
/// <summary> | ||||||
/// Set the unique battleId of each battle | ||||||
/// </summary> | ||||||
public ulong NextBattleId | ||||||
{ | ||||||
get => State.GetUInt64(nameof(NextBattleId)); | ||||||
private set => State.SetUInt64(nameof(NextBattleId), value); | ||||||
} | ||||||
|
||||||
public Arena(ISmartContractState smartContractState, uint maxUsers) : base(smartContractState) | ||||||
{ | ||||||
Owner = Message.Sender; | ||||||
MaxUsers = maxUsers; | ||||||
NextBattleId = 1; | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Only owner can set new owner and new owner will be in pending state | ||||||
/// till new owner will call <see cref="ClaimOwnership"></see> method. | ||||||
/// </summary> | ||||||
/// <param name="newOwner">The new owner which is going to be in pending state</param> | ||||||
public void SetPendingOwner(Address newOwner) | ||||||
{ | ||||||
EnsureOwnerOnly(); | ||||||
PendingOwner = newOwner; | ||||||
|
||||||
Log(new OwnershipTransferRequestedLog { CurrentOwner = Owner, PendingOwner = newOwner }); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Waiting be called after new owner is requested by <see cref="SetPendingOwner"/> call. | ||||||
/// Pending owner will be new owner after successfull call. | ||||||
/// </summary> | ||||||
public void ClaimOwnership() | ||||||
{ | ||||||
var newOwner = PendingOwner; | ||||||
|
||||||
Assert(newOwner == Message.Sender, "ClaimOwnership must be called by the new(pending) owner."); | ||||||
|
||||||
var oldOwner = Owner; | ||||||
Owner = newOwner; | ||||||
PendingOwner = Address.Zero; | ||||||
|
||||||
Log(new OwnershipTransferedLog { PreviousOwner = oldOwner, NewOwner = newOwner }); | ||||||
} | ||||||
/// <summary> | ||||||
/// Battle owner will start the battle | ||||||
/// </summary> | ||||||
public ulong StartBattle(ulong fee) | ||||||
{ | ||||||
Assert(Message.Sender == Owner, "Only battle owner can start game."); | ||||||
Assert(fee < ulong.MaxValue / MaxUsers, "Fee is too high"); | ||||||
|
||||||
var battleId = NextBattleId; | ||||||
NextBattleId += 1; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or alternatively you can use below code.
Suggested change
|
||||||
|
||||||
var battle = new BattleMain | ||||||
{ | ||||||
BattleId = battleId, | ||||||
Fee = fee, | ||||||
Users = new Address[MaxUsers] | ||||||
}; | ||||||
SetBattle(battleId, battle); | ||||||
|
||||||
Log(new BattleStartedLog { BattleId = battleId, Address = Message.Sender }); | ||||||
return battleId; | ||||||
} | ||||||
/// <summary> | ||||||
/// 4 different user will enter the battle | ||||||
/// </summary> | ||||||
public void EnterBattle(ulong battleId) | ||||||
{ | ||||||
var battle = GetBattle(battleId); | ||||||
|
||||||
Assert(battle.Winner == Address.Zero, "Battle not found."); | ||||||
|
||||||
Assert(battle.Fee == Message.Value, "Battle fee is not matching with entry fee paid."); | ||||||
|
||||||
var user = GetUser(battleId, Message.Sender); | ||||||
|
||||||
Assert(!user.ScoreSubmitted, "The user already submitted score."); | ||||||
|
||||||
SetUser(battleId, Message.Sender, user); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are sending user address 2 times in here. One is Message.Sender and other is as user parameter with Address property. I question it is needed to add Address property for user ? Because you can not get/set user without knowing the address already. |
||||||
|
||||||
var userindex = GetUserIndex(battleId); | ||||||
Assert(userindex != MaxUsers, "Max user reached for this battle."); | ||||||
battle.Users.SetValue(Message.Sender, userindex); | ||||||
SetUserIndex(battleId, userindex + 1); | ||||||
|
||||||
SetBattle(battleId, battle); | ||||||
|
||||||
Log(new BattleEnteredLog { BattleId = battleId, Address = Message.Sender }); | ||||||
} | ||||||
/// <summary> | ||||||
/// 4 different user will end the battle and submit the score | ||||||
/// </summary> | ||||||
public void EndBattle(Address userAddress, ulong battleId, uint score) | ||||||
{ | ||||||
Assert(Message.Sender == Owner, "Only battle owner can end game."); | ||||||
|
||||||
var ScoreSubmittedCount = GetScoreSubmittedCount(battleId); | ||||||
Assert(ScoreSubmittedCount < MaxUsers, "All users already submitted score."); | ||||||
|
||||||
var battle = GetBattle(battleId); | ||||||
|
||||||
Assert(battle.Winner == Address.Zero, "Battle not found."); | ||||||
|
||||||
var user = GetUser(battleId, userAddress); | ||||||
|
||||||
Assert(!user.ScoreSubmitted, "The user already submitted score."); | ||||||
|
||||||
user.ScoreSubmitted = true; | ||||||
|
||||||
SetUser(battleId, userAddress, user); | ||||||
|
||||||
ScoreSubmittedCount += 1; | ||||||
SetScoreSubmittedCount(battleId, ScoreSubmittedCount); | ||||||
|
||||||
var highestScorer = GetHighestScorer(battleId); | ||||||
|
||||||
if (score > highestScorer.Score) | ||||||
{ | ||||||
highestScorer.Score = score; | ||||||
highestScorer.HighestScorer = userAddress; | ||||||
highestScorer.HighestScoreCount = 1; | ||||||
|
||||||
SetHighestScorer(battleId, highestScorer); | ||||||
} | ||||||
else if (score == highestScorer.Score) | ||||||
{ | ||||||
highestScorer.HighestScoreCount++; | ||||||
SetHighestScorer(battleId, highestScorer); | ||||||
} | ||||||
|
||||||
if (ScoreSubmittedCount == MaxUsers) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have to assert ScoreSubmittedCount > MaxUsers case at the beginning of the method ? |
||||||
{ | ||||||
highestScorer = GetHighestScorer(battleId); | ||||||
if (highestScorer.HighestScoreCount > 1) | ||||||
CancelBattle(battle); | ||||||
else | ||||||
ProcessWinner(battle, highestScorer.HighestScorer); | ||||||
} | ||||||
|
||||||
Log(new BattleEndedLog { BattleId = battleId, Address = Message.Sender }); | ||||||
} | ||||||
/// <summary> | ||||||
/// Get winner address | ||||||
/// </summary> | ||||||
public Address GetWinner(ulong battleId) | ||||||
{ | ||||||
var battle = GetBattle(battleId); | ||||||
return battle.Winner; | ||||||
} | ||||||
/// <summary> | ||||||
/// Process winner when all user scores are submitted | ||||||
/// </summary> | ||||||
private void ProcessWinner(BattleMain battle, Address winnerAddress) | ||||||
{ | ||||||
battle.Winner = winnerAddress; | ||||||
SetBattle(battle.BattleId, battle); | ||||||
ProcessPrize(battle); | ||||||
} | ||||||
/// <summary> | ||||||
/// Send 3/4 amount to winner and 1/4 amount to battle owner | ||||||
/// </summary> | ||||||
private void ProcessPrize(BattleMain battle) | ||||||
{ | ||||||
var prize = battle.Fee * (MaxUsers - 1); | ||||||
Transfer(battle.Winner, prize); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If winner address is a contract address then this transfer has possibility to fail. So you can use withdrawal pattern(pull) in here. It will transfer funds immediately if destination is not a contract address and if it is then it will fallback to withdrawal pattern.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Winner address will not be a contract address. 4 players will play the game with their wallet address. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how do you ensure that user addresses will not belong to a another contract ? |
||||||
Transfer(Owner, battle.Fee); | ||||||
} | ||||||
/// <summary> | ||||||
/// Cancel battle and refund the fee amount | ||||||
/// </summary> | ||||||
private void CancelBattle(BattleMain battle) | ||||||
{ | ||||||
battle.IsCancelled = true; | ||||||
SetBattle(battle.BattleId, battle); | ||||||
|
||||||
Transfer(battle.Users[0], battle.Fee); | ||||||
Transfer(battle.Users[1], battle.Fee); | ||||||
Transfer(battle.Users[2], battle.Fee); | ||||||
Transfer(battle.Users[3], battle.Fee); | ||||||
} | ||||||
private void EnsureOwnerOnly() | ||||||
{ | ||||||
Assert(Message.Sender == Owner, "The method is owner only."); | ||||||
} | ||||||
public struct BattleMain | ||||||
{ | ||||||
public ulong BattleId; | ||||||
public Address Winner; | ||||||
public Address[] Users; | ||||||
public ulong Fee; | ||||||
public bool IsCancelled; | ||||||
} | ||||||
public struct BattleUser | ||||||
{ | ||||||
public bool ScoreSubmitted; | ||||||
} | ||||||
public struct BattleHighestScorer | ||||||
{ | ||||||
public uint Score; | ||||||
public uint HighestScoreCount; | ||||||
public Address HighestScorer; | ||||||
} | ||||||
public struct OwnershipTransferedLog | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo
Suggested change
|
||||||
{ | ||||||
[Index] public Address PreviousOwner; | ||||||
[Index] public Address NewOwner; | ||||||
} | ||||||
public struct OwnershipTransferRequestedLog | ||||||
{ | ||||||
[Index] public Address CurrentOwner; | ||||||
[Index] public Address PendingOwner; | ||||||
} | ||||||
public struct BattleStartedLog | ||||||
{ | ||||||
[Index] public ulong BattleId; | ||||||
[Index] public Address Address; | ||||||
} | ||||||
public struct BattleEnteredLog | ||||||
{ | ||||||
[Index] public ulong BattleId; | ||||||
[Index] public Address Address; | ||||||
} | ||||||
public struct BattleEndedLog | ||||||
{ | ||||||
[Index] public ulong BattleId; | ||||||
[Index] public Address Address; | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netcoreapp2.1</TargetFramework> | ||
|
||
<LangVersion>8.0</LangVersion> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Stratis.SmartContracts" Version="2.0.0" /> | ||
</ItemGroup> | ||
</Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You access value of NextBattleId twice in this code so please follow my recommandation.