Skip to content
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

Decks Onchain #134

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bddea36
Deck struct now also consists of name. Named airdropped deck.
eviterin Mar 2, 2024
ceaa86a
Added ability to fetch all decks of a given player from chain
eviterin Mar 2, 2024
032db1d
Can now save deck onchain
eviterin Mar 2, 2024
08a9b2e
Can now load decks
eviterin Mar 2, 2024
157e8ad
Now shows modal while saving deck
eviterin Mar 3, 2024
6bdbd82
Fixed a bug where player could save a deck and then try loading it be…
eviterin Mar 3, 2024
4123b00
Modify deck support
eviterin Mar 3, 2024
e0f5d3c
Moved some logic related to decks from ´collection´ to ´decklist´. ad…
eviterin Mar 3, 2024
7d0bf6d
Added two new functions. One to fetch all deck names a player owns an…
eviterin Mar 4, 2024
a906dac
Implemented interface to call the added functions in
eviterin Mar 4, 2024
29a78ea
No longer loads all the deck of the player, but instead loads all the…
eviterin Mar 4, 2024
5288304
Show loading modal when loading deck
eviterin Mar 4, 2024
af4f3da
Added a meter that displays deck validity
eviterin Mar 8, 2024
22af73c
updated to NOT run deck validity checks on deck creation/modification.
eviterin Mar 8, 2024
8bb08d9
checkDeck function also verifies deck min size. joinGame function n…
eviterin Mar 8, 2024
48818f1
Added an indicator that the player's decks are loading
eviterin Mar 8, 2024
c2cbafb
Removed console.logs
eviterin Mar 8, 2024
fa856de
Set default name for deck
eviterin Mar 8, 2024
0ccfd78
Added spacing between decks
eviterin Mar 8, 2024
ba4dc7d
Save and cancel buttons now have different colors
eviterin Mar 8, 2024
e7b320c
Removed incorrect deck size limits and small button label changes
eviterin Mar 8, 2024
0b5d9a1
Fixed a bug where the loading indicator would be prematurely hidden. …
eviterin Mar 26, 2024
f812094
Renamed getDeck to getDeckCards. Renamed getDeckReal to getDeck.
eviterin Mar 26, 2024
851f71c
Resolved all warnings when running make check. Then ran linter in web…
eviterin Mar 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/contracts/src/DeckAirdrop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ contract DeckAirdrop is Ownable {
cards[i] = first + i;
}

inventory.addDeck(msg.sender, Inventory.Deck(cards));
inventory.addDeck(msg.sender, Inventory.Deck("Starter Deck", cards));
}
}
4 changes: 3 additions & 1 deletion packages/contracts/src/Game.sol
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,8 @@ contract Game {
revert Errors.NotAllowedToJoin();
}

inventory.checkDeck(msg.sender, deckID);

// Update gdata.players, but not gdata.livePlayers, which is used to determine if the
// game is ready to start (all players have joined & drawn their initial hand).
gdata.players.push(msg.sender);
Expand All @@ -547,7 +549,7 @@ contract Game {
uint256[] storage cards = gdata.cards;
pdata.deckStart = uint8(cards.length);
inventory.checkDeck(msg.sender, deckID);
uint256[] memory deck = inventory.getDeck(msg.sender, deckID);
uint256[] memory deck = inventory.getDeckCards(msg.sender, deckID);

for (uint256 i = 0; i < deck.length; i++) {
cards.push(deck[i]);
Expand Down
39 changes: 35 additions & 4 deletions packages/contracts/src/Inventory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ contract Inventory is Ownable {
// We need a struct because Solidity is unable to copy an array from memory to storage
// directly, but can do it when the array is embedded in a struct.
struct Deck {
string name;
uint256[] cards;
}

Expand Down Expand Up @@ -188,6 +189,9 @@ contract Inventory is Ownable {
// ---------------------------------------------------------------------------------------------

function checkDeckSize(Deck storage deck) internal view {
if (deck.cards.length < MIN_DECK_SIZE) {
revert SmallDeckEnergy();
}
if (deck.cards.length > MAX_DECK_SIZE) {
revert BigDeckEnergy();
}
Expand Down Expand Up @@ -215,8 +219,7 @@ contract Inventory is Ownable {
revert OutOfDeckIDs();
}
deckID = uint8(longDeckID);
decks[player].push();
_addDeck(player, deckID, deck);
decks[player].push(deck);
emit DeckAdded(player, deckID);
}

Expand All @@ -243,7 +246,7 @@ contract Inventory is Ownable {
exists(player, deckID)
notInGame(player)
{
_addDeck(player, deckID, deck);
decks[player][deckID] = deck;
emit DeckRemoved(player, deckID);
emit DeckAdded(player, deckID);
}
Expand Down Expand Up @@ -328,7 +331,7 @@ contract Inventory is Ownable {
// ---------------------------------------------------------------------------------------------

// Returns the list of cards in the given deck of the given player.
function getDeck(address player, uint8 deckID)
function getDeckCards(address player, uint8 deckID)
external
view
exists(player, deckID)
Expand All @@ -339,13 +342,41 @@ contract Inventory is Ownable {

// ---------------------------------------------------------------------------------------------

// Returns the list of cards in the given deck of the given player.
function getDeck(address player, uint8 deckID) external view exists(player, deckID) returns (Deck memory) {
return decks[player][deckID];
}

// ---------------------------------------------------------------------------------------------

// Returns the decks of a given player.
function getAllDecks(address player) external view returns (Deck[] memory) {
return decks[player];
}

// ---------------------------------------------------------------------------------------------

// Returns the number of deck a player has created.
function getNumDecks(address player) external view returns (uint8) {
return uint8(decks[player].length);
}

// ---------------------------------------------------------------------------------------------

// Returns the names of all decks for a given player.
function getDeckNames(address player) external view returns (string[] memory) {
uint256 deckCount = decks[player].length;
string[] memory deckNames = new string[](deckCount);

for (uint256 i = 0; i < deckCount; i++) {
deckNames[i] = decks[player][i].name;
}

return deckNames;
}

// ---------------------------------------------------------------------------------------------

function getCardTypes(uint256[] memory cardIDArr) public view returns (uint256[] memory) {
uint256 len = cardIDArr.length;
uint256[] memory cardTypeArr = new uint256[](len);
Expand Down
4 changes: 2 additions & 2 deletions packages/contracts/src/test/Integration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ contract Integration is Test {
game.createGame(2);

// IDs don't start at 1 because the deploy script currently airdrops some addresses, might change.
inventory.getDeck(player2, 0); // player1 has card id of 49-72 inclusive
inventory.getDeck(player2, 0); // player2 has card id of 73-96 inclusive
inventory.getDeckCards(player2, 0); // player1 has card id of 49-72 inclusive
inventory.getDeckCards(player2, 0); // player2 has card id of 73-96 inclusive

vm.startPrank(player1);
game.joinGame(gameID, DECK_ID, SALT_HASH, JOIN_DATA);
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/src/test/Inventory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract InventoryTest is Test {
// expect revert if player's deck contains a card with more than `MAX_CARD_COPY` copies.
function testCheckDeckExceedsMaxCopy() public {
uint8 deckId = 0;
uint256 randomCard = inventory.getDeck(player1, deckId)[2];
uint256 randomCard = inventory.getDeckCards(player1, deckId)[2];

// increase card `randomCard` copies to 4
vm.startPrank(player1);
Expand Down
158 changes: 158 additions & 0 deletions packages/webapp/src/actions/getDeck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { defaultErrorHandling } from "src/actions/errors"
import { contractWriteThrowing } from "src/actions/libContractWrite"
import { Address } from "src/chain"
import { deployment } from "src/deployment"
import { inventoryABI } from "src/generated"

// =================================================================================================

export type GetDeckArgs = {
playerAddress: Address
onSuccess: () => void
}

export type GetDeckAtArgs = {
playerAddress: Address
onSuccess: () => void
index: number
}

// -------------------------------------------------------------------------------------------------

/**
* Fetches all decks of the given player by sending the `getAllDecks` transaction.
*
* Returns `true` iff the transaction is successful.
*/
export async function getAllDecks(args: GetDeckArgs): Promise<any> {
try {
return await getAllDecksImpl(args)
} catch (err) {
defaultErrorHandling("getAllDecks", err)
return false
}
}

/**
* Fetches the deck of the given player of a given ID by sending the `getDeck` transaction.
*
* Returns `true` iff the transaction is successful.
*/
export async function getDeck(args: GetDeckAtArgs): Promise<any> {
try {
return await getDeckImpl(args)
} catch (err) {
defaultErrorHandling("getDeck", err)
return false
}
}

// -------------------------------------------------------------------------------------------------

/**
* Fetches deck count of the given player by sending the `getNumDecks` transaction.
*
* Returns `true` iff the transaction is successful.
*/
export async function getNumDecks(args: GetDeckArgs): Promise<any> {
try {
return await getNumDecksImpl(args)
} catch (err) {
defaultErrorHandling("getNumDecks", err)
return false
}
}

// -------------------------------------------------------------------------------------------------

/**
* Fetches deck count of the given player by sending the `getNumDecks` transaction.
*
* Returns `true` iff the transaction is successful.
*/
export async function getDeckNames(args: GetDeckArgs): Promise<any> {
try {
return await getDeckNamesImpl(args)
} catch (err) {
defaultErrorHandling("getDeckNames", err)
return false
}
}

// -------------------------------------------------------------------------------------------------

async function getAllDecksImpl(args: GetDeckArgs): Promise<any> {
try {
const result = await contractWriteThrowing({
contract: deployment.Inventory,
abi: inventoryABI,
functionName: "getAllDecks",
args: [args.playerAddress],
})

args.onSuccess()
return result
} catch (error) {
console.error("Error fetching decks:", error)
return null
}
}

// -------------------------------------------------------------------------------------------------

async function getDeckImpl(args: GetDeckAtArgs): Promise<any> {
try {
const result = await contractWriteThrowing({
contract: deployment.Inventory,
abi: inventoryABI,
functionName: "getDeck",
args: [args.playerAddress, args.index],
})

args.onSuccess()
return result
} catch (error) {
console.error("Error fetching deck:", error)
return null
}
}

// -------------------------------------------------------------------------------------------------

async function getNumDecksImpl(args: GetDeckArgs): Promise<any> {
try {
const result = await contractWriteThrowing({
contract: deployment.Inventory,
abi: inventoryABI,
functionName: "getNumDecks",
args: [args.playerAddress],
})

args.onSuccess()
return result
} catch (error) {
console.error("Error fetching decks:", error)
return null
}
}

// -------------------------------------------------------------------------------------------------

async function getDeckNamesImpl(args: GetDeckArgs): Promise<any> {
try {
const result = await contractWriteThrowing({
contract: deployment.Inventory,
abi: inventoryABI,
functionName: "getDeckNames",
args: [args.playerAddress],
})

args.onSuccess()
return result
} catch (error) {
console.error("Error fetching decks:", error)
return null
}
}

// =================================================================================================
90 changes: 90 additions & 0 deletions packages/webapp/src/actions/setDeck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { defaultErrorHandling } from "src/actions/errors"
import { contractWriteThrowing } from "src/actions/libContractWrite"
import { Address } from "src/chain"
import { deployment } from "src/deployment"
import { inventoryABI } from "src/generated"
import { checkFresh, freshWrap } from "src/store/checkFresh"
import { Deck } from "src/store/types"

// =================================================================================================

export type SaveArgs = {
deck: Deck
playerAddress: Address
onSuccess: () => void
}

export type ModifyArgs = {
deck: Deck
playerAddress: Address
index: number
onSuccess: () => void
}

// -------------------------------------------------------------------------------------------------

/**
* Saves a deck created by the player by sending the `saveDeck` transaction.
*
* Returns `true` iff the transaction is successful.
*/
export async function save(args: SaveArgs): Promise<boolean> {
try {
return await saveImpl(args)
} catch (err) {
return defaultErrorHandling("save", err)
}
}

/**
* Modifies a deck owned by the player by sending the `modifyDeck` transaction.
*
* Returns `true` iff the transaction is successful.
*/
export async function modify(args: ModifyArgs): Promise<boolean> {
try {
return await modifyImpl(args)
} catch (err) {
return defaultErrorHandling("modify", err)
}
}

// -------------------------------------------------------------------------------------------------

async function saveImpl(args: SaveArgs): Promise<boolean> {
const cardBigInts = args.deck.cards.map((card) => card.id)

checkFresh(
await freshWrap(
contractWriteThrowing({
contract: deployment.Inventory,
abi: inventoryABI,
functionName: "addDeck",
args: [args.playerAddress, { name: args.deck.name, cards: cardBigInts }],
})
)
)

args.onSuccess()
return true
}

async function modifyImpl(args: ModifyArgs): Promise<boolean> {
const cardBigInts = args.deck.cards.map((card) => card.id)
console.log("INDEX: " + args.index)
checkFresh(
await freshWrap(
contractWriteThrowing({
contract: deployment.Inventory,
abi: inventoryABI,
functionName: "replaceDeck",
args: [args.playerAddress, args.index, { name: args.deck.name, cards: cardBigInts }],
})
)
)

args.onSuccess()
return true
}

// =================================================================================================
Loading