Skip to content

Commit

Permalink
Merge pull request #94 from moonstream-to/new-invite-only
Browse files Browse the repository at this point in the history
Adding sessions requiring signature to join.
  • Loading branch information
kellan-simiotics authored Dec 13, 2023
2 parents 2963221 + 2be794e commit c4098aa
Show file tree
Hide file tree
Showing 6 changed files with 378 additions and 66 deletions.
45 changes: 42 additions & 3 deletions python/fullcount/Fullcount.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,12 @@ def join_session(
session_id: int,
nft_address: ChecksumAddress,
token_id: int,
signature: bytes,
transaction_config,
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.joinSession(
session_id, nft_address, token_id, transaction_config
session_id, nft_address, token_id, signature, transaction_config
)

def pitch_hash(
Expand Down Expand Up @@ -289,6 +290,12 @@ def sample_outcome_from_distribution(
nonce0, nonce1, distribution, block_identifier=block_number
)

def session_hash(
self, session_id: int, block_number: Optional[Union[str, int]] = "latest"
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.sessionHash.call(session_id, block_identifier=block_number)

def session_progress(
self, session_id: int, block_number: Optional[Union[str, int]] = "latest"
) -> Any:
Expand All @@ -298,11 +305,16 @@ def session_progress(
)

def start_session(
self, nft_address: ChecksumAddress, token_id: int, role: int, transaction_config
self,
nft_address: ChecksumAddress,
token_id: int,
role: int,
require_signature: bool,
transaction_config,
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.startSession(
nft_address, token_id, role, transaction_config
nft_address, token_id, role, require_signature, transaction_config
)

def swing_hash(
Expand Down Expand Up @@ -548,6 +560,7 @@ def handle_join_session(args: argparse.Namespace) -> None:
session_id=args.session_id,
nft_address=args.nft_address,
token_id=args.token_id,
signature=args.signature,
transaction_config=transaction_config,
)
print(result)
Expand Down Expand Up @@ -635,6 +648,15 @@ def handle_sample_outcome_from_distribution(args: argparse.Namespace) -> None:
print(result)


def handle_session_hash(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Fullcount(args.address)
result = contract.session_hash(
session_id=args.session_id, block_number=args.block_number
)
print(result)


def handle_session_progress(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Fullcount(args.address)
Expand All @@ -652,6 +674,7 @@ def handle_start_session(args: argparse.Namespace) -> None:
nft_address=args.nft_address,
token_id=args.token_id,
role=args.role,
require_signature=args.require_signature,
transaction_config=transaction_config,
)
print(result)
Expand Down Expand Up @@ -808,6 +831,9 @@ def generate_cli() -> argparse.ArgumentParser:
join_session_parser.add_argument(
"--token-id", required=True, help="Type: uint256", type=int
)
join_session_parser.add_argument(
"--signature", required=True, help="Type: bytes", type=bytes_argument_type
)
join_session_parser.set_defaults(func=handle_join_session)

pitch_hash_parser = subcommands.add_parser("pitch-hash")
Expand Down Expand Up @@ -900,6 +926,13 @@ def generate_cli() -> argparse.ArgumentParser:
func=handle_sample_outcome_from_distribution
)

session_hash_parser = subcommands.add_parser("session-hash")
add_default_arguments(session_hash_parser, False)
session_hash_parser.add_argument(
"--session-id", required=True, help="Type: uint256", type=int
)
session_hash_parser.set_defaults(func=handle_session_hash)

session_progress_parser = subcommands.add_parser("session-progress")
add_default_arguments(session_progress_parser, False)
session_progress_parser.add_argument(
Expand All @@ -918,6 +951,12 @@ def generate_cli() -> argparse.ArgumentParser:
start_session_parser.add_argument(
"--role", required=True, help="Type: uint8", type=int
)
start_session_parser.add_argument(
"--require-signature",
required=True,
help="Type: bool",
type=boolean_argument_type,
)
start_session_parser.set_defaults(func=handle_start_session)

swing_hash_parser = subcommands.add_parser("swing-hash")
Expand Down
6 changes: 3 additions & 3 deletions python/fullcount/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import argparse

from . import BeerLeagueBallers, Fullcount, generation_1, randomness, generators, commit
from . import BeerLeagueBallers, Fullcount, generation_1, randomness, generators, signatures


def generate_cli() -> argparse.ArgumentParser:
Expand Down Expand Up @@ -28,8 +28,8 @@ def generate_cli() -> argparse.ArgumentParser:
codegen_parser = generators.generate_cli()
subparsers.add_parser("codegen", parents=[codegen_parser], add_help=False)

commit_parser = commit.generate_cli()
subparsers.add_parser("commit", parents=[commit_parser], add_help=False)
signatures_parser = signatures.generate_cli()
subparsers.add_parser("signatures", parents=[signatures_parser], add_help=False)

return parser

Expand Down
33 changes: 29 additions & 4 deletions python/fullcount/commit.py → python/fullcount/signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ def sign_message(message_hash, signer) -> str:
)
return signed_message_bytes.hex()

def sign_session(
contract: Fullcount,
signer: Any,
sessionID: int
) -> Tuple[str, str]:
"""
Generates a session signature suitable for joining a session requiring a signature
"""
message_hash = contract.session_hash(
sessionID
)
return message_hash, sign_message(message_hash, signer)

def commit_pitch(
contract: Fullcount,
Expand All @@ -39,7 +51,6 @@ def commit_pitch(
)
return message_hash, sign_message(message_hash, signer)


def commit_swing(
contract: Fullcount,
signer: Any,
Expand All @@ -56,6 +67,15 @@ def commit_swing(
)
return message_hash, sign_message(message_hash, signer)

def handle_sign_session(args: argparse.Namespace) -> None:
network.connect(args.network)
signer = network.accounts.load(args.sender, args.password)
contract = Fullcount(args.address)
session_id = args.session_id
message_hash, signature = sign_session(
contract, signer, session_id
)
print(f"Message hash: {message_hash}\nSignature: {signature}")

def handle_commit_pitch(args: argparse.Namespace) -> None:
network.connect(args.network)
Expand Down Expand Up @@ -98,11 +118,16 @@ def handle_commit_swing(args: argparse.Namespace) -> None:


def generate_cli() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Generate pitch and swing commitments")
parser = argparse.ArgumentParser(description="Generates session, commit-pitch, and commit-swing signatures.")
parser.set_defaults(func=lambda _: parser.print_help())

subparsers = parser.add_subparsers()

session_parser = subparsers.add_parser("sign-session")
add_default_arguments(session_parser, transact=True)
session_parser.add_argument("--session-id", type=int, required=True, help="Session ID")
session_parser.set_defaults(func=handle_sign_session)

pitch_type_help = (
f"Pitch type. If not specified, generates a random pitch type. Inputs: "
+ ", ".join(
Expand All @@ -124,7 +149,7 @@ def generate_cli() -> argparse.ArgumentParser:
+ ", ".join(f"{hor.value} - {hor.name}" for hor in list(HorizontalLocation))
)

pitch_parser = subparsers.add_parser("pitch")
pitch_parser = subparsers.add_parser("commit-pitch")
add_default_arguments(pitch_parser, transact=True)
pitch_parser.add_argument("--commit-nonce", type=int, required=True, help="Nonce")
pitch_parser.add_argument("--commit-type", type=int, help=pitch_type_help)
Expand All @@ -144,7 +169,7 @@ def generate_cli() -> argparse.ArgumentParser:
)
pitch_parser.set_defaults(func=handle_commit_pitch)

swing_parser = subparsers.add_parser("swing")
swing_parser = subparsers.add_parser("commit-swing")
add_default_arguments(swing_parser, transact=True)
swing_parser.add_argument("--commit-nonce", type=int, required=True, help="Nonce")
swing_parser.add_argument("--commit-type", type=int, help=swing_type_help)
Expand Down
73 changes: 65 additions & 8 deletions src/Fullcount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ contract Fullcount is EIP712 {
// Session ID => session state
// NOTE: Sessions are 1-indexed
mapping(uint256 => Session) public SessionState;
mapping(uint256 => bool) SessionRequiresSignature;

// ERC721 address => ERC721 token ID => session that that character is staked into
// NOTE: Sessions are 1-indexed
Expand Down Expand Up @@ -129,7 +130,7 @@ contract Fullcount is EIP712 {
} else if (session.phaseStartTimestamp + SecondsPerPhase < block.timestamp) {
return 6;
} else if (session.pitcherNFT.nftAddress == address(0) || session.batterNFT.nftAddress == address(0)) {
if(session.pitcherLeftSession || session.batterLeftSession) {
if (session.pitcherLeftSession || session.batterLeftSession) {
return 1;
} else {
return 2;
Expand All @@ -147,9 +148,23 @@ contract Fullcount is EIP712 {
return _sessionProgress(sessionID);
}

function sessionHash(uint256 sessionID) public view returns (bytes32) {
bytes32 structHash = keccak256(abi.encode(keccak256("SessionMessage(uint256 session)"), uint256(sessionID)));
return _hashTypedDataV4(structHash);
}

// Emits:
// - SessionStarted
function startSession(address nftAddress, uint256 tokenID, PlayerType role) external virtual returns (uint256) {
function startSession(
address nftAddress,
uint256 tokenID,
PlayerType role,
bool requireSignature
)
external
virtual
returns (uint256)
{
require(_isTokenOwner(nftAddress, tokenID), "Fullcount.startSession: msg.sender is not NFT owner");

require(StakedSession[nftAddress][tokenID] == 0, "Fullcount.startSession: NFT is already staked to a session.");
Expand All @@ -168,6 +183,8 @@ contract Fullcount is EIP712 {

StakedSession[nftAddress][tokenID] = NumSessions;

SessionRequiresSignature[NumSessions] = requireSignature;

SessionState[NumSessions].phaseStartTimestamp = block.timestamp;

emit SessionStarted(NumSessions, nftAddress, tokenID, role);
Expand All @@ -177,20 +194,46 @@ contract Fullcount is EIP712 {

// Emits:
// - SessionJoined
function joinSession(uint256 sessionID, address nftAddress, uint256 tokenID) external virtual {
function joinSession(
uint256 sessionID,
address nftAddress,
uint256 tokenID,
bytes memory signature
)
external
virtual
{
require(sessionID <= NumSessions, "Fullcount.joinSession: session does not exist");

require(_isTokenOwner(nftAddress, tokenID), "Fullcount.joinSession: msg.sender is not NFT owner");

require(StakedSession[nftAddress][tokenID] == 0, "Fullcount.joinSession: NFT is already staked to a session.");

Session storage session = SessionState[sessionID];

if (session.pitcherNFT.nftAddress != address(0) && session.batterNFT.nftAddress != address(0)) {
revert("Fullcount.joinSession: session is already full");
} else if (session.pitcherLeftSession || session.batterLeftSession) {
revert("Fullcount.joinSession: opponent left session");
}

if (SessionRequiresSignature[sessionID]) {
address sessionStarter;
if (session.pitcherNFT.nftAddress != address(0)) {
sessionStarter = IERC721(session.pitcherNFT.nftAddress).ownerOf(session.pitcherNFT.tokenID);
} else if (session.batterNFT.nftAddress != address(0)) {
sessionStarter = IERC721(session.batterNFT.nftAddress).ownerOf(session.batterNFT.tokenID);
} else {
revert("Fullcount.joinSession: idiot programmer");
}

bytes32 sessionMessageHash = sessionHash(sessionID);
require(
SignatureChecker.isValidSignatureNow(sessionStarter, sessionMessageHash, signature),
"Fullcount.joinSession: invalid signature in session requiring signature to join."
);
}

PlayerType role = PlayerType.Pitcher;
if (session.batterNFT.nftAddress == address(0)) {
role = PlayerType.Batter;
Expand Down Expand Up @@ -257,7 +300,9 @@ contract Fullcount is EIP712 {
);
_unstakeNFT(SessionState[sessionID].pitcherNFT.nftAddress, SessionState[sessionID].pitcherNFT.tokenID);
} else if (SessionState[sessionID].batterNFT.nftAddress != address(0)) {
emit SessionAborted(sessionID, SessionState[sessionID].batterNFT.nftAddress, SessionState[sessionID].batterNFT.tokenID);
emit SessionAborted(
sessionID, SessionState[sessionID].batterNFT.nftAddress, SessionState[sessionID].batterNFT.tokenID
);
_unstakeNFT(SessionState[sessionID].batterNFT.nftAddress, SessionState[sessionID].batterNFT.tokenID);
} else {
revert("Fullcount.abortSession: idiot programmer");
Expand Down Expand Up @@ -339,7 +384,10 @@ contract Fullcount is EIP712 {

Session storage session = SessionState[sessionID];

require(_isTokenOwner(session.pitcherNFT.nftAddress, session.pitcherNFT.tokenID), "Fullcount.commitPitch: msg.sender is not pitcher NFT owner");
require(
_isTokenOwner(session.pitcherNFT.nftAddress, session.pitcherNFT.tokenID),
"Fullcount.commitPitch: msg.sender is not pitcher NFT owner"
);

require(!session.didPitcherCommit, "Fullcount.commitPitch: pitcher already committed");

Expand All @@ -362,7 +410,10 @@ contract Fullcount is EIP712 {

Session storage session = SessionState[sessionID];

require(_isTokenOwner(session.batterNFT.nftAddress, session.batterNFT.tokenID), "Fullcount.commitSwing: msg.sender is not batter NFT owner");
require(
_isTokenOwner(session.batterNFT.nftAddress, session.batterNFT.tokenID),
"Fullcount.commitSwing: msg.sender is not batter NFT owner"
);

require(!session.didBatterCommit, "Fullcount.commitSwing: batter already committed");

Expand Down Expand Up @@ -509,7 +560,10 @@ contract Fullcount is EIP712 {

Session storage session = SessionState[sessionID];

require(_isTokenOwner(session.pitcherNFT.nftAddress, session.pitcherNFT.tokenID), "Fullcount.revealPitch: msg.sender is not pitcher NFT owner");
require(
_isTokenOwner(session.pitcherNFT.nftAddress, session.pitcherNFT.tokenID),
"Fullcount.revealPitch: msg.sender is not pitcher NFT owner"
);

require(!session.didPitcherReveal, "Fullcount.revealPitch: pitcher already revealed");

Expand Down Expand Up @@ -561,7 +615,10 @@ contract Fullcount is EIP712 {

Session storage session = SessionState[sessionID];

require(_isTokenOwner(session.batterNFT.nftAddress, session.batterNFT.tokenID), "Fullcount.revealSwing: msg.sender is not batter NFT owner");
require(
_isTokenOwner(session.batterNFT.nftAddress, session.batterNFT.tokenID),
"Fullcount.revealSwing: msg.sender is not batter NFT owner"
);

require(!session.didBatterReveal, "Fullcount.revealSwing: batter already revealed");

Expand Down
Loading

0 comments on commit c4098aa

Please sign in to comment.