Skip to content

Commit

Permalink
contract changes
Browse files Browse the repository at this point in the history
  • Loading branch information
arjanjohan committed Mar 24, 2024
1 parent 7547018 commit 7e16b49
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 254 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

⚽ Ten Taipei Football Manager is an on-chain football manager game that leverages the privacy that TEN Protocol provides to hide players' strategies from opponents.

\*\* This project is build using frontend components from a previous hackathon project [Super Leo Lig](https://github.com/arjanjohan/aleo-football). All smart contracts and contract interactions are created from scratch during this hackathon.

⚙️ Built using Solidity, Hardhat, wagmi, React and Javascript.

- 🧾 **Privacy on TEN**: Players commit to a strategy privately using TEN Protocol.
Expand Down Expand Up @@ -67,7 +69,7 @@ To test the smart contract interactions via a script, execute these scripts:

## Deployed contracts

- TEN deployed contract 0x492bD2595393678F4E7ef2a2D3136860D4d83378 in tx 0x651f86c420c77861bccc9e1f36eff6dcfaa3af4387fd0c0b9aa02c9402667c29
- TEN deployed contract 0x40B10e8A06fBE442D82Eda1D547A278Ce372CA9A in tx 0xec761365ad14fca87c5dcbbe08a781b4bebe971bf88923e1ee5903fd4ad39fe0
- [Scroll](https://sepolia.scrollscan.com/address/0x1806a13729aDBC602e079F5d00FbA9345BE7381c#code)
- [Linea](https://goerli.lineascan.build/address/address/0x1e61235A37ee5642d71c6c3f060b6E94b05EE6E7#code)
- [Optimism](https://sepolia-optimism.etherscan.io/address/0xab2EE87906222B433AF6836b1f1588b79294f67e)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/c88bf9a7a4247ccbdfdb64e7d147a284.json"
"buildInfo": "../../build-info/90aa1822c4e1fe324a84be9d2a6c51ca.json"
}

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/hardhat/deploy/00_deploy_your_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
const { deployer } = await hre.getNamedAccounts();
const { deploy } = hre.deployments;

const tokenContractAddress = "0x3F19064e7490DCCb5eA03E9D1B24f96e71e0170a"; // Replace this with the actual Token contract address
const tokenContractAddress = "0x479a15a13358561c3fe9B982b69c3da191aB4F92"; // Replace this with the actual Token contract address
const blocks = 100; // Set to 100 blocks (approx 20-25 minutes) for testing purposes

await deploy("FootballGame", {
Expand Down
241 changes: 80 additions & 161 deletions packages/hardhat/deployments/ten/FootballGame.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n"
},
"contracts/FootballGame.sol": {
"content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract FootballGame {\n\taddress public owner;\n\tIERC20 public footballCoin;\n\tuint public timelockBlocks;\n\n\tenum Status {\n\t\tProposed,\n\t\tAccepted,\n\t\tFinished,\n\t\tFinishedByTimelock\n\t}\n\n\tstruct Game {\n\t\tuint256 gameId;\n\t\taddress challenger;\n\t\taddress opponent;\n\t\tuint256 wagerAmount;\n\t\tuint256[] challengerFormation;\n\t\tuint256[] opponentFormation;\n\t\tGameResult result;\n\t\tuint blockNumber;\n\t\tStatus status;\n\t}\n\n\tstruct GameResult {\n\t\tuint goalsHomeTeam;\n\t\tuint goalsAwayTeam;\n\t}\n\n\t///////////////\n\t// MAPPINGS //\n\t///////////////\n\n\tmapping(uint256 => uint256[]) private challenger_formation;\n\tmapping(uint256 => Game) public games;\n\tuint256 public gameCount;\n\n\tfunction getGameCount() public view returns (uint256) {\n\t\treturn gameCount;\n\t}\n\n\t///////////////\n\t// EVENTS //\n\t///////////////\n\n\tevent GameProposed(\n\t\tuint256 gameId,\n\t\taddress indexed sender,\n\t\taddress indexed recipient,\n\t\tuint256 wagerAmount,\n\t\tuint blockNumber\n\t);\n\n\tevent GameAccepted(\n\t\tuint256 gameId,\n\t\taddress indexed sender,\n\t\taddress indexed recipient,\n\t\tuint256 wagerAmount,\n\t\tuint blockNumber\n\t);\n\n\tevent GameFinished(\n\t\tuint256 gameId,\n\t\taddress indexed sender,\n\t\taddress indexed recipient\n\t);\n\n\tevent GameFinishedByTimelock(\n\t\tuint256 gameId,\n\t\taddress indexed sender,\n\t\taddress indexed recipient\n\t);\n\n\t///////////////\n\t// PLAYERS //\n\t///////////////\n\n\tstruct Player {\n\t\tuint256 player_id;\n\t\tuint256 team_id;\n\t\tuint256 position; // 0 = Empty, 1 = Goalkeeper, 2 = Defense, 3 = Midfield, 4 = Attack\n\t\tuint128 attack;\n\t\tuint128 defense;\n\t\tuint128 speed;\n\t\tuint128 power;\n\t\tuint128 stamina;\n\t\tuint128 technique;\n\t\tuint128 goalkeeping;\n\t}\n\n\tmapping(uint256 => Player) public players;\n\n\tfunction addPlayer(\n\t\tuint256 playerId,\n\t\tuint256 teamId,\n\t\tuint256 position,\n\t\tuint128 attack,\n\t\tuint128 defense,\n\t\tuint128 speed,\n\t\tuint128 power,\n\t\tuint128 stamina,\n\t\tuint128 technique,\n\t\tuint128 goalkeeping\n\t) public onlyOwner {\n\t\tPlayer memory newPlayer = Player({\n\t\t\tplayer_id: playerId,\n\t\t\tteam_id: teamId,\n\t\t\tposition: position,\n\t\t\tattack: attack,\n\t\t\tdefense: defense,\n\t\t\tspeed: speed,\n\t\t\tpower: power,\n\t\t\tstamina: stamina,\n\t\t\ttechnique: technique,\n\t\t\tgoalkeeping: goalkeeping\n\t\t});\n\n\t\tplayers[playerId] = newPlayer;\n\t}\n\n\t///////////////\n\t// CONSTRUCTOR //\n\t///////////////\n\n\tconstructor(IERC20 _footballCoin, uint _timelockBlocks) {\n\t\towner = msg.sender;\n\t\tfootballCoin = _footballCoin;\n\t\ttimelockBlocks = _timelockBlocks;\n\t}\n\tmodifier onlyOwner() {\n\t\trequire(msg.sender == owner, \"Caller is not the owner\");\n\t\t_;\n\t}\n\n\t///////////////\n\t// GAME FUNCTIONS //\n\t///////////////\n\n\tfunction proposeGame(\n\t\taddress opponent,\n\t\tuint256 wagerAmount,\n\t\tuint256[] memory formation\n\t) public {\n\t\trequire(\n\t\t\tfootballCoin.allowance(msg.sender, address(this)) >= wagerAmount,\n\t\t\t\"Insufficient allowance for wager\"\n\t\t);\n\t\tfor (uint i = 0; i < formation.length; i++) {\n\t\t\trequire(players[formation[i]].player_id != 0, \"Invalid player id\");\n\t\t}\n\n\t\t// Transfer the wager amount from the challenger to the contract\n\t\tfootballCoin.transferFrom(msg.sender, address(this), wagerAmount);\n\n\t\tuint256 newGameId = ++gameCount;\n\t\tgames[newGameId] = Game({\n\t\t\tgameId: newGameId,\n\t\t\tchallenger: msg.sender,\n\t\t\topponent: opponent,\n\t\t\twagerAmount: wagerAmount,\n\t\t\tchallengerFormation: new uint256[](0),\n\t\t\topponentFormation: new uint256[](0),\n\t\t\tresult: GameResult({ goalsHomeTeam: 0, goalsAwayTeam: 0 }),\n\t\t\tblockNumber: block.number,\n\t\t\tstatus: Status.Proposed\n\t\t});\n\n\t\tchallenger_formation[newGameId] = formation;\n\n\t\t// Emitting events to notify about the new game proposal\n\t\temit GameProposed(\n\t\t\tnewGameId,\n\t\t\tmsg.sender,\n\t\t\topponent,\n\t\t\twagerAmount,\n\t\t\tblock.number\n\t\t);\n\t}\n\n\tfunction acceptGame(uint256 gameId, uint256[] memory formation) public {\n\t\tGame storage game = games[gameId];\n\n\t\trequire(\n\t\t\tmsg.sender == game.opponent,\n\t\t\t\"Only the opponent can accept the game\"\n\t\t);\n\t\trequire(\n\t\t\tfootballCoin.allowance(msg.sender, address(this)) >=\n\t\t\t\tgame.wagerAmount,\n\t\t\t\"Insufficient allowance for wager\"\n\t\t);\n\t\trequire(game.status == Status.Proposed, \"Incorrect state\");\n\n\t\tfor (uint i = 0; i < formation.length; i++) {\n\t\t\trequire(players[formation[i]].player_id != 0, \"Invalid player id\");\n\t\t}\n\n\t\t// Transfer the wager amount from the opponent to the contract\n\t\tfootballCoin.transferFrom(msg.sender, address(this), game.wagerAmount);\n\n\t\tgame.opponentFormation = formation;\n\t\tgame.blockNumber = block.number;\n\t\tgame.status = Status.Accepted;\n\n\t\t// Notify about game acceptance\n\t\temit GameAccepted(\n\t\t\tgameId,\n\t\t\tmsg.sender,\n\t\t\tgame.challenger,\n\t\t\tgame.wagerAmount,\n\t\t\tblock.number\n\t\t);\n\t}\n\n\tfunction revealOutcome(uint256 gameId) public {\n\t\tGame storage game = games[gameId];\n\n\t\trequire(\n\t\t\tmsg.sender == game.challenger,\n\t\t\t\"Only the challenger can reveal the outcome\"\n\t\t);\n\t\trequire(game.status == Status.Accepted, \"Incorrect state\");\n\n\t\tgame.challengerFormation = challenger_formation[gameId];\n\n\t\tGameResult memory result = determineGameResult(game);\n\t\tgame.result = result;\n\n\t\tgame.status = Status.Finished;\n\n\t\taddress winner = determineWinner(game, result);\n\t\tpayoutWinners(game, winner);\n\n\t\temit GameFinished(gameId, msg.sender, game.opponent);\n\t}\n\n\tfunction opponentClaimTimelock(uint256 gameId) public {\n\t\tGame storage game = games[gameId];\n\n\t\trequire(\n\t\t\tmsg.sender == game.opponent,\n\t\t\t\"Only the opponent can claim the timelock\"\n\t\t);\n\t\trequire(\n\t\t\tblock.number > game.blockNumber + timelockBlocks,\n\t\t\t\"Opponent can claim timelock only after timelockBlocks have passed\"\n\t\t);\n\n\t\tgame.status = Status.FinishedByTimelock;\n\n\t\tpayoutWinners(game, game.opponent);\n\n\t\temit GameFinishedByTimelock(gameId, msg.sender, game.challenger);\n\t}\n\n\t///////////////\n\t// HELPER FUNCTIONS //\n\t///////////////\n\n\tfunction determineGameResult(\n\t\tGame storage game\n\t) private view returns (GameResult memory) {\n\t\tGameResult memory result = GameResult({\n\t\t\tgoalsHomeTeam: 0,\n\t\t\tgoalsAwayTeam: 0\n\t\t});\n\n\t\t// Player memory homeGoalkeeper = players[game.challengerFormation[1]];\n\t\t// Player memory awayGoalkeeper = players[game.opponentFormation[1]];\n\t\tbytes32 prevRandao = blockhash(block.number - 1);\n\t\tfor (uint i = 1; i < game.challengerFormation.length; i++) {\n\t\t\tPlayer memory homePlayer = players[game.challengerFormation[i]];\n\t\t\tPlayer memory awayPlayer = players[\n\t\t\t\tgame.opponentFormation[game.challengerFormation.length - i]\n\t\t\t];\n\t\t\tuint256 totalspeed = homePlayer.speed + awayPlayer.speed;\n\t\t\tuint256 randomSpeed = uint256(prevRandao) % totalspeed;\n\t\t\tif (homePlayer.speed < randomSpeed) {\n\t\t\t\t// Home player attacks\n\t\t\t\tif (homePlayer.attack > awayPlayer.defense) {\n\t\t\t\t\tresult.goalsHomeTeam++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Away player attacks\n\t\t\t\tif (awayPlayer.attack > homePlayer.defense) {\n\t\t\t\t\tresult.goalsAwayTeam++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tfunction determineWinner(\n\t\tGame storage game,\n\t\tGameResult memory result\n\t) private view returns (address) {\n\t\tif (result.goalsHomeTeam > result.goalsAwayTeam) {\n\t\t\treturn game.challenger;\n\t\t} else if (result.goalsHomeTeam < result.goalsAwayTeam) {\n\t\t\treturn game.opponent;\n\t\t}\n\t\treturn address(0); // It's a draw\n\t}\n\n\tfunction payoutWinners(Game storage game, address winner) private {\n\t\tif (winner == address(0)) {\n\t\t\trequire(\n\t\t\t\tfootballCoin.transfer(game.challenger, game.wagerAmount),\n\t\t\t\t\"Transfer to challenger failed\"\n\t\t\t);\n\t\t\trequire(\n\t\t\t\tfootballCoin.transfer(game.opponent, game.wagerAmount),\n\t\t\t\t\"Transfer to opponent failed\"\n\t\t\t);\n\t\t} else {\n\t\t\tuint256 totalPot = game.wagerAmount * 2;\n\t\t\trequire(\n\t\t\t\tfootballCoin.transfer(winner, totalPot),\n\t\t\t\t\"Transfer to winner failed\"\n\t\t\t);\n\t\t}\n\t}\n}\n"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata",
"devdoc",
"userdoc",
"storageLayout",
"evm.gasEstimates"
],
"": [
"ast"
]
}
},
"metadata": {
"useLiteralContent": true
}
}
}
12 changes: 6 additions & 6 deletions packages/hardhat/scripts/gameFlow.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ async function main() {
const footballCoinAddress = addresses.FootballCoin;
const challengerAddress = addresses.ChallengerAddress;
const opponentAddress = addresses.OpponentAddress;
const mintAmount = "5000000000000000000"; // 1 token in Wei
const wagerAmount = "1000000000000000000"; // 1 token in Wei
const mintAmount = "2000000000000000000"; // 1 token in Wei
const wagerAmount = "2000000000000000000"; // 1 token in Wei
const defaultFormation = [1, 4, 5, 6, 7 , 8 ,9 ,10, 11, 12 ,13]
const challenger_signer = (await ethers.getSigners())[0];
const opponent_signer = (await ethers.getSigners())[1];
Expand All @@ -24,8 +24,8 @@ async function main() {

await new Promise(resolve => setTimeout(resolve, 1000));
// Mint and approve tokens for challenger
const mintTx = await footballCoin.mint(challengerAddress, mintAmount);
await mintTx.wait();
// const mintTx = await footballCoin.mint(challengerAddress, mintAmount);
// await mintTx.wait();


await new Promise(resolve => setTimeout(resolve, 1000));
Expand All @@ -47,8 +47,8 @@ async function main() {

await new Promise(resolve => setTimeout(resolve, 3000));
// Mint and approve tokens for challenger
const mintTx2 = await footballCoin.mint(opponentAddress, mintAmount);
await mintTx2.wait();
// const mintTx2 = await footballCoin.mint(opponentAddress, mintAmount);
// await mintTx2.wait();

await new Promise(resolve => setTimeout(resolve, 3000));
const approveTx2 = await footballCoin.approve(footballGameAddress, mintAmount);
Expand Down
90 changes: 8 additions & 82 deletions packages/nextjs/contracts/deployedContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1565,7 +1565,7 @@ const deployedContracts = {
},
},
FootballGame: {
address: "0x0238E08bFCd6252c70c3A1C30eeD44D70fe53156",
address: "0x40B10e8A06fBE442D82Eda1D547A278Ce372CA9A",
abi: [
{
inputs: [
Expand Down Expand Up @@ -1783,30 +1783,6 @@ const deployedContracts = {
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "uint256",
name: "numberOfValues",
type: "uint256",
},
{
internalType: "bytes32",
name: "prevRandao",
type: "bytes32",
},
],
name: "extractRandomValues",
outputs: [
{
internalType: "uint256[]",
name: "",
type: "uint256[]",
},
],
stateMutability: "pure",
type: "function",
},
{
inputs: [],
name: "footballCoin",
Expand Down Expand Up @@ -1841,32 +1817,13 @@ const deployedContracts = {
type: "uint256",
},
],
name: "gameResults",
name: "games",
outputs: [
{
internalType: "uint256",
name: "goalsHomeTeam",
type: "uint256",
},
{
internalType: "uint256",
name: "goalsAwayTeam",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "uint256",
name: "",
name: "gameId",
type: "uint256",
},
],
name: "games",
outputs: [
{
internalType: "address",
name: "challenger",
Expand All @@ -1882,11 +1839,6 @@ const deployedContracts = {
name: "wagerAmount",
type: "uint256",
},
{
internalType: "bool",
name: "isFinished",
type: "bool",
},
{
components: [
{
Expand All @@ -1909,6 +1861,11 @@ const deployedContracts = {
name: "blockNumber",
type: "uint256",
},
{
internalType: "enum FootballGame.Status",
name: "status",
type: "uint8",
},
],
stateMutability: "view",
type: "function",
Expand All @@ -1926,37 +1883,6 @@ const deployedContracts = {
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "uint256",
name: "gameId",
type: "uint256",
},
],
name: "getGameResult",
outputs: [
{
components: [
{
internalType: "uint256",
name: "goalsHomeTeam",
type: "uint256",
},
{
internalType: "uint256",
name: "goalsAwayTeam",
type: "uint256",
},
],
internalType: "struct FootballGame.GameResult",
name: "",
type: "tuple",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
Expand Down

0 comments on commit 7e16b49

Please sign in to comment.