-
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 3 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,271 @@ | ||||||
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 const uint MaxUsers = 4; | ||||||
public struct BattleMain | ||||||
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. All struct definitions should go to bottom of the class. |
||||||
{ | ||||||
public ulong BattleId; | ||||||
public Address Winner; | ||||||
public Address[] Users; | ||||||
public ulong Fee; | ||||||
public bool IsCancelled; | ||||||
} | ||||||
public struct BattleUser | ||||||
{ | ||||||
public uint Score; | ||||||
public bool ScoreSubmitted; | ||||||
} | ||||||
public struct BattleHighestScorer | ||||||
{ | ||||||
public uint Score; | ||||||
public Address[] Scorers; | ||||||
} | ||||||
public struct ClaimPendingDeployerOwnershipLog | ||||||
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. This log type name should be not changed. It should stays as in original. |
||||||
{ | ||||||
[Index] public Address From; | ||||||
[Index] public Address To; | ||||||
} | ||||||
public struct SetPendingDeployerOwnershipLog | ||||||
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. This log type name should be not changed. It should stays as in original. |
||||||
{ | ||||||
[Index] public Address From; | ||||||
[Index] public Address To; | ||||||
} | ||||||
public struct BattleEventLog | ||||||
{ | ||||||
[Index] public string Event; | ||||||
[Index] public ulong BattleId; | ||||||
[Index] public Address Address; | ||||||
} | ||||||
/// <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); | ||||||
} | ||||||
/// <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) : base(smartContractState) | ||||||
{ | ||||||
Owner = Message.Sender; | ||||||
NextBattleId = 1; | ||||||
} | ||||||
public void SetPendingOwnership(Address pendingOwner) | ||||||
{ | ||||||
EnsureOwnerOnly(); | ||||||
|
||||||
PendingOwner = pendingOwner; | ||||||
|
||||||
Log(new SetPendingDeployerOwnershipLog { From = Message.Sender, To = pendingOwner }); | ||||||
} | ||||||
public void ClaimPendingOwnership() | ||||||
{ | ||||||
var pendingOwner = PendingOwner; | ||||||
|
||||||
Assert(Message.Sender == pendingOwner, "HASHBATTLE: UNAUTHORIZED"); | ||||||
|
||||||
var oldOwner = Owner; | ||||||
|
||||||
Owner = pendingOwner; | ||||||
PendingOwner = Address.Zero; | ||||||
|
||||||
Log(new ClaimPendingDeployerOwnershipLog { From = oldOwner, To = pendingOwner }); | ||||||
} | ||||||
/// <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. You access value of NextBattleId twice in this code so please follow my recommandation. 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 BattleEventLog { Event = "Start", 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)); | ||||||
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.
Suggested change
|
||||||
|
||||||
SetBattle(battleId, battle); | ||||||
|
||||||
Log(new BattleEventLog { Event = "Enter", BattleId = battleId, Address = Message.Sender }); | ||||||
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.
Suggested change
|
||||||
} | ||||||
/// <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 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.Score = score; | ||||||
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. What for we save Score on user ? It seems there is no use case for it. |
||||||
user.ScoreSubmitted = true; | ||||||
|
||||||
SetUser(battleId, userAddress, user); | ||||||
|
||||||
var ScoreSubmittedCount = GetScoreSubmittedCount(battleId); | ||||||
ScoreSubmittedCount += 1; | ||||||
SetScoreSubmittedCount(battleId, ScoreSubmittedCount); | ||||||
|
||||||
var highestScorer = GetHighestScorer(battleId); | ||||||
|
||||||
if (score > highestScorer.Score) | ||||||
SetHighestScorer(battleId, new BattleHighestScorer { Scorers = new Address[] { userAddress }, Score = score }); | ||||||
else if (score == highestScorer.Score) | ||||||
{ | ||||||
var scorers = highestScorer.Scorers; | ||||||
Array.Resize(ref scorers, scorers.Length + 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. This allocate new array actually and iterate it by copying fully. We should avoid it. What you need to is highest score and how many times same highest score counted and highest user. You can keep last highest one always whenever equal score submited. So you can do something like this
|
||||||
scorers[scorers.Length - 1] = userAddress; | ||||||
SetHighestScorer(battleId, new BattleHighestScorer { Scorers = scorers, Score = score }); | ||||||
} | ||||||
|
||||||
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.Scorers.Length > 1) | ||||||
CancelBattle(battle); | ||||||
else | ||||||
ProcessWinner(battle, highestScorer.Scorers[0]); | ||||||
} | ||||||
|
||||||
Log(new BattleEventLog { Event = "End", BattleId = battleId, Address = Message.Sender }); | ||||||
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. It is usually recommended way to log by type name like |
||||||
} | ||||||
/// <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 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); | ||||||
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. Always use
Suggested change
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. Also these are getters and setters method over State so please move them to top of constructor. |
||||||
} | ||||||
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}"); | ||||||
} | ||||||
private void EnsureOwnerOnly() | ||||||
{ | ||||||
Assert(Message.Sender == Owner, "HASHBATTLE: UNAUTHORIZED"); | ||||||
} | ||||||
} |
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.
If there is possibility in future that MaxUsers can be higher than 4 than you can make it constructor parameter.