From 267f41fea1722068f57fed8bb4719fe0aa673acf Mon Sep 17 00:00:00 2001 From: Morazzela Date: Wed, 24 Aug 2022 19:00:45 +0200 Subject: [PATCH 01/21] start leaderboard contract --- .gitignore | 1 + contracts/competition/Competition.sol | 138 ++++++++++++++++++++++++++ scripts/core/deployCompetition.js | 52 ++++++++++ 3 files changed, 191 insertions(+) create mode 100644 contracts/competition/Competition.sol create mode 100644 scripts/core/deployCompetition.js diff --git a/.gitignore b/.gitignore index 8e91b0e9..2b69fa71 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ data/snapshotBalance data/holders flattened distribution-data*.json +.DS_Store #Hardhat files cache diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol new file mode 100644 index 00000000..b5ffaa42 --- /dev/null +++ b/contracts/competition/Competition.sol @@ -0,0 +1,138 @@ +pragma solidity ^0.6.0; + +import "../referrals/interfaces/IReferralStorage.sol"; + +contract Competition +{ + uint public start; + uint public end; + uint public registrationStart; + uint public registrationEnd; + IReferralStorage private referralStorage; + + struct Team { + address leader; + string name; + bytes32 referral; + address[] members; + address[] joinRequests; + } + + address[] private leaders; + mapping(address => Team) private teams; + mapping(string => bool) private teamNames; + mapping(address => address) private membersToTeam; + mapping(address => mapping(address => bool)) private requests; + + modifier registrationIsOpen() + { + require(block.timestamp >= registrationStart, "Registration is not opened yet."); + require(block.timestamp < registrationEnd, "Registration is closed."); + _; + } + + modifier isNotLeader() + { + require(teams[msg.sender].leader == address(0), "Team leaders are not allowed."); + _; + } + + modifier isNotMember() + { + require(membersToTeam[msg.sender] == address(0), "Team members are not allowed."); + _; + } + + constructor ( + uint _start, + uint _end, + uint _registrationStart, + uint _registrationEnd, + IReferralStorage _referralStorage + ) public + { + start = _start; + end = _end; + registrationStart = _registrationStart; + registrationEnd = _registrationEnd; + referralStorage = _referralStorage; + } + + function registerTeam (string calldata name, bytes32 referral) external registrationIsOpen isNotLeader + { + require(referralStorage.codeOwners(referral) != address(0), "Referral code does not exist."); + require(teamNames[name] == false, "Team name already registered."); + + Team storage team; + + team.leader = msg.sender; + team.name = name; + team.referral = referral; + team.members.push(msg.sender); + + teams[msg.sender] = team; + leaders.push(msg.sender); + teamNames[name] = true; + } + + function createJoinRequest (address leaderAddress) external registrationIsOpen isNotLeader isNotMember + { + require(membersToTeam[msg.sender] == address(0), "You can't join multiple teams."); + require(teams[msg.sender].leader != address(0), "The team does not exist."); + require(requests[leaderAddress][msg.sender] == false, "You already applied for this team."); + + teams[leaderAddress].joinRequests.push(msg.sender); + requests[leaderAddress][msg.sender] = true; + } + + function approveJoinRequest (address memberAddress) external registrationIsOpen isNotMember + { + require(requests[msg.sender][memberAddress] == false, "This member did not apply."); + require(membersToTeam[memberAddress] == address(0), "This member already joined a team."); + + referralStorage.setTraderReferralCode(memberAddress, teams[msg.sender].referral); + teams[msg.sender].members.push(msg.sender); + membersToTeam[memberAddress] = msg.sender; + } + + function getLeaders() external view returns (address[] memory) + { + address[] memory res; + + for (uint i = 0; i < leaders.length; i++) { + res[i] = leaders[i]; + } + + return res; + } + + function getTeam(address leaderAddr) external view returns (address leader, string memory name, bytes32 referral) + { + Team memory team = teams[leaderAddr]; + return (team.leader, team.name, team.referral); + } + + function getMemberTeam(address memberAddr) external view returns (address) + { + return membersToTeam[memberAddr]; + } + + function getTeamMembers(address leaderAddr) external view returns (address[] memory) + { + return teams[leaderAddr].members; + } + + function getTeamJoinRequests(address leaderAddr) external view returns (address[] memory) + { + address[] memory res; + + for (uint i = 0; i < teams[leaderAddr].joinRequests.length; i++) { + address jr = teams[leaderAddr].joinRequests[i]; + if (membersToTeam[jr] == address(0)) { + res[i] = jr; + } + } + + return res; + } +} diff --git a/scripts/core/deployCompetition.js b/scripts/core/deployCompetition.js new file mode 100644 index 00000000..64b6268b --- /dev/null +++ b/scripts/core/deployCompetition.js @@ -0,0 +1,52 @@ +const { deployContract, contractAt } = require("../shared/helpers") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); + +async function getArbValues() { + const positionRouter = await contractAt("PositionRouter", "0x3D6bA331e3D9702C5e8A8d254e5d8a285F223aba") + + return { positionRouter } +} + +async function getAvaxValues() { + const positionRouter = await contractAt("PositionRouter", "0x195256074192170d1530527abC9943759c7167d8") + + return { positionRouter } +} + +async function getValues() { + if (network === "arbitrum") { + return getArbValues() + } + + if (network === "avax") { + return getAvaxValues() + } + + if (network === "testnet") { + return getTestnetValues() + } +} + +async function main() { + const { positionRouter } = await getValues() + const referralStorage = await contractAt("ReferralStorage", await positionRouter.referralStorage()) + + const startTime = Math.round(Date.now() / 1000) + const endTime = startTime + 100000000000 + + await deployContract("Competition", [ + startTime, + endTime, + startTime, + endTime, + referralStorage.address, + ]); +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) From ca263fcb391651d47e455977c9cc5251f0460fdb Mon Sep 17 00:00:00 2001 From: Morazzela Date: Wed, 24 Aug 2022 23:57:18 +0200 Subject: [PATCH 02/21] contract changes & began tests --- contracts/competition/Competition.sol | 93 +++++++++++++-------------- test/competition/Competition.js | 88 +++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 48 deletions(-) create mode 100644 test/competition/Competition.js diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index b5ffaa42..7c29302c 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -1,15 +1,11 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../referrals/interfaces/IReferralStorage.sol"; +import "../access/Governable.sol"; -contract Competition +contract Competition is Governable { - uint public start; - uint public end; - uint public registrationStart; - uint public registrationEnd; - IReferralStorage private referralStorage; - struct Team { address leader; string name; @@ -18,6 +14,11 @@ contract Competition address[] joinRequests; } + uint public start; + uint public end; + uint public registrationStart; + uint public registrationEnd; + IReferralStorage private referralStorage; address[] private leaders; mapping(address => Team) private teams; mapping(string => bool) private teamNames; @@ -26,14 +27,7 @@ contract Competition modifier registrationIsOpen() { - require(block.timestamp >= registrationStart, "Registration is not opened yet."); - require(block.timestamp < registrationEnd, "Registration is closed."); - _; - } - - modifier isNotLeader() - { - require(teams[msg.sender].leader == address(0), "Team leaders are not allowed."); + require(block.timestamp >= registrationStart && block.timestamp < registrationEnd, "Registration is closed."); _; } @@ -43,23 +37,21 @@ contract Competition _; } - constructor ( - uint _start, - uint _end, - uint _registrationStart, - uint _registrationEnd, - IReferralStorage _referralStorage - ) public - { - start = _start; - end = _end; - registrationStart = _registrationStart; - registrationEnd = _registrationEnd; - referralStorage = _referralStorage; + constructor( + uint start_, + uint end_, + uint registrationStart_, + uint registrationEnd_, + IReferralStorage referralStorage_ + ) public { + start = start_; + end = end_; + registrationStart = registrationStart_; + registrationEnd = registrationEnd_; + referralStorage = referralStorage_; } - function registerTeam (string calldata name, bytes32 referral) external registrationIsOpen isNotLeader - { + function registerTeam(string calldata name, bytes32 referral) external registrationIsOpen { require(referralStorage.codeOwners(referral) != address(0), "Referral code does not exist."); require(teamNames[name] == false, "Team name already registered."); @@ -75,8 +67,7 @@ contract Competition teamNames[name] = true; } - function createJoinRequest (address leaderAddress) external registrationIsOpen isNotLeader isNotMember - { + function createJoinRequest(address leaderAddress) external registrationIsOpen isNotMember { require(membersToTeam[msg.sender] == address(0), "You can't join multiple teams."); require(teams[msg.sender].leader != address(0), "The team does not exist."); require(requests[leaderAddress][msg.sender] == false, "You already applied for this team."); @@ -85,8 +76,7 @@ contract Competition requests[leaderAddress][msg.sender] = true; } - function approveJoinRequest (address memberAddress) external registrationIsOpen isNotMember - { + function approveJoinRequest(address memberAddress) external registrationIsOpen isNotMember { require(requests[msg.sender][memberAddress] == false, "This member did not apply."); require(membersToTeam[memberAddress] == address(0), "This member already joined a team."); @@ -95,35 +85,26 @@ contract Competition membersToTeam[memberAddress] = msg.sender; } - function getLeaders() external view returns (address[] memory) - { + function getLeaders(uint start_, uint offset) external view returns (address[] memory) { address[] memory res; - for (uint i = 0; i < leaders.length; i++) { + for (uint i = start_; i < leaders.length && i < start_ + offset; i++) { res[i] = leaders[i]; } return res; } - function getTeam(address leaderAddr) external view returns (address leader, string memory name, bytes32 referral) - { + function getTeam(address leaderAddr) external view returns (address leader, string memory name, bytes32 referral) { Team memory team = teams[leaderAddr]; return (team.leader, team.name, team.referral); } - function getMemberTeam(address memberAddr) external view returns (address) - { - return membersToTeam[memberAddr]; - } - - function getTeamMembers(address leaderAddr) external view returns (address[] memory) - { + function getTeamMembers(address leaderAddr) external view returns (address[] memory) { return teams[leaderAddr].members; } - function getTeamJoinRequests(address leaderAddr) external view returns (address[] memory) - { + function getTeamJoinRequests(address leaderAddr) external view returns (address[] memory) { address[] memory res; for (uint i = 0; i < teams[leaderAddr].joinRequests.length; i++) { @@ -135,4 +116,20 @@ contract Competition return res; } + + function setStart(uint start_) external onlyGov { + start = start_; + } + + function setEnd(uint end_) external onlyGov { + end = end_; + } + + function setRegistrationStart(uint registrationStart_) external onlyGov { + registrationStart = registrationStart_; + } + + function setRegistrationEnd(uint registrationEnd_) external onlyGov { + registrationEnd = registrationEnd_; + } } diff --git a/test/competition/Competition.js b/test/competition/Competition.js new file mode 100644 index 00000000..301c63c0 --- /dev/null +++ b/test/competition/Competition.js @@ -0,0 +1,88 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { getBlockTime } = require("../shared/utilities") + +use(solidity) + +const { keccak256 } = ethers.utils + +// Tier0 (5% discount, 5% rebate) = Tier {totalRebate = 1000, defaultTradersDiscountShare = 5000} +// Tier1 (12% discount, 8% rebate) = Tier {totalRebate = 2000, defaultTradersDiscountShare = 6000} +// Tier2 (12% discount, 15% rebate) = Tier {totalRebate = 2700, defaultTradersDiscountShare = 4444} +// for the last tier extra EsGMX incentives will be handled off-chain +describe("Competition", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2] = provider.getWallets() + let competition + let referralStorage + + beforeEach(async () => { + const ts = await getBlockTime(provider) + + referralStorage = await deployContract("ReferralStorage", []) + competition = await deployContract("Competition", [ + ts + 60, // start + ts + 120, // end + ts - 60, // registrationStart + ts + 60, // registrationEnd + referralStorage.address + ]); + }) + + it("allows owner to set times", async () => { + await competition.connect(wallet).setStart(1) + await competition.connect(wallet).setEnd(1) + await competition.connect(wallet).setRegistrationStart(1) + await competition.connect(wallet).setRegistrationEnd(1) + }) + + it("disable non owners to set times", async () => { + await expect(competition.connect(user0).setStart(1)).to.be.revertedWith("Governable: forbidden") + await expect(competition.connect(user0).setEnd(1)).to.be.revertedWith("Governable: forbidden") + await expect(competition.connect(user0).setRegistrationStart(1)).to.be.revertedWith("Governable: forbidden") + await expect(competition.connect(user0).setRegistrationEnd(1)).to.be.revertedWith("Governable: forbidden") + }) + + it("disable people to register teams before registration time", async () => { + const code = keccak256("0xFF") + await referralStorage.connect(user0).registerCode(code) + await competition.connect(wallet).setRegistrationStart((await getBlockTime(provider)) + 10) + await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") + }) + + it("disable people to register teams after registration time", async () => { + const code = keccak256("0xFF") + await referralStorage.connect(user0).registerCode(code) + await competition.connect(wallet).setRegistrationEnd((await getBlockTime(provider)) - 10) + await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") + }) + + it("allows people to register teams in times", async () => { + const code = keccak256("0xFF") + await referralStorage.connect(user0).registerCode(code) + try { + await competition.connect(user0).registerTeam("1", code) + } catch (e) { + console.log(e) + } + }) + + it("disabled people to register multiple teams", async () => { + const code = keccak256("0xFF") + await referralStorage.connect(user0).registerCode(code) + await competition.connect(user0).registerTeam("1", code) + await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Team members are not allowed.") + }) + + it("disabled people to register a team with non existing referral code", async () => { + const code = keccak256("0xFF") + await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Referral code does not exist.") + }) + + it("disabled multiple teams with the same name", async () => { + const code = keccak256("0xFF") + await competition.connect(user0).registerTeam("1", code) + await expect(competition.connect(user1).registerTeam("1", code)).to.be.revertedWith("Team name already registered.") + }) +}); From 7290fc3972915504367b94189cba795e81710c57 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Thu, 25 Aug 2022 22:00:16 +0200 Subject: [PATCH 03/21] continuing tests & contract --- contracts/competition/Competition.sol | 25 +++++------ test/competition/Competition.js | 64 ++++++++++++++++++++------- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index 7c29302c..a60c9bf8 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -18,12 +18,12 @@ contract Competition is Governable uint public end; uint public registrationStart; uint public registrationEnd; - IReferralStorage private referralStorage; - address[] private leaders; - mapping(address => Team) private teams; - mapping(string => bool) private teamNames; - mapping(address => address) private membersToTeam; - mapping(address => mapping(address => bool)) private requests; + IReferralStorage public referralStorage; + address[] public leaders; + mapping(address => Team) public teams; + mapping(string => bool) public teamNames; + mapping(address => address) public membersToTeam; + mapping(address => mapping(address => bool)) public requests; modifier registrationIsOpen() { @@ -51,33 +51,32 @@ contract Competition is Governable referralStorage = referralStorage_; } - function registerTeam(string calldata name, bytes32 referral) external registrationIsOpen { + function registerTeam(string calldata name, bytes32 referral) external registrationIsOpen isNotMember { require(referralStorage.codeOwners(referral) != address(0), "Referral code does not exist."); require(teamNames[name] == false, "Team name already registered."); - Team storage team; - + Team storage team = teams[msg.sender]; team.leader = msg.sender; team.name = name; team.referral = referral; team.members.push(msg.sender); - teams[msg.sender] = team; leaders.push(msg.sender); teamNames[name] = true; + membersToTeam[msg.sender] = msg.sender; } function createJoinRequest(address leaderAddress) external registrationIsOpen isNotMember { require(membersToTeam[msg.sender] == address(0), "You can't join multiple teams."); - require(teams[msg.sender].leader != address(0), "The team does not exist."); + require(teams[leaderAddress].leader != address(0), "The team does not exist."); require(requests[leaderAddress][msg.sender] == false, "You already applied for this team."); teams[leaderAddress].joinRequests.push(msg.sender); requests[leaderAddress][msg.sender] = true; } - function approveJoinRequest(address memberAddress) external registrationIsOpen isNotMember { - require(requests[msg.sender][memberAddress] == false, "This member did not apply."); + function approveJoinRequest(address memberAddress) external registrationIsOpen { + require(requests[msg.sender][memberAddress] == true, "This member did not apply."); require(membersToTeam[memberAddress] == address(0), "This member already joined a team."); referralStorage.setTraderReferralCode(memberAddress, teams[msg.sender].referral); diff --git a/test/competition/Competition.js b/test/competition/Competition.js index 301c63c0..f31bed42 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -16,6 +16,7 @@ describe("Competition", function () { const [wallet, user0, user1, user2] = provider.getWallets() let competition let referralStorage + let code = keccak256("0xFF") beforeEach(async () => { const ts = await getBlockTime(provider) @@ -28,6 +29,8 @@ describe("Competition", function () { ts + 60, // registrationEnd referralStorage.address ]); + + await referralStorage.registerCode(code) }) it("allows owner to set times", async () => { @@ -45,22 +48,16 @@ describe("Competition", function () { }) it("disable people to register teams before registration time", async () => { - const code = keccak256("0xFF") - await referralStorage.connect(user0).registerCode(code) await competition.connect(wallet).setRegistrationStart((await getBlockTime(provider)) + 10) await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) it("disable people to register teams after registration time", async () => { - const code = keccak256("0xFF") - await referralStorage.connect(user0).registerCode(code) await competition.connect(wallet).setRegistrationEnd((await getBlockTime(provider)) - 10) await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) it("allows people to register teams in times", async () => { - const code = keccak256("0xFF") - await referralStorage.connect(user0).registerCode(code) try { await competition.connect(user0).registerTeam("1", code) } catch (e) { @@ -68,21 +65,58 @@ describe("Competition", function () { } }) - it("disabled people to register multiple teams", async () => { - const code = keccak256("0xFF") - await referralStorage.connect(user0).registerCode(code) + it("disable people to register multiple teams", async () => { await competition.connect(user0).registerTeam("1", code) - await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Team members are not allowed.") + await expect(competition.connect(user0).registerTeam("2", code)).to.be.revertedWith("Team members are not allowed.") }) - it("disabled people to register a team with non existing referral code", async () => { - const code = keccak256("0xFF") - await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Referral code does not exist.") + it("disable people to register a team with non existing referral code", async () => { + await expect(competition.connect(user0).registerTeam("1", keccak256("0xFE"))).to.be.revertedWith("Referral code does not exist.") }) - it("disabled multiple teams with the same name", async () => { - const code = keccak256("0xFF") + it("disable multiple teams with the same name", async () => { await competition.connect(user0).registerTeam("1", code) await expect(competition.connect(user1).registerTeam("1", code)).to.be.revertedWith("Team name already registered.") }) + + it("allows people to create join requests", async () => { + await competition.connect(user0).registerTeam("1", code) + await competition.connect(user1).registerTeam("2", code) + await competition.connect(user2).createJoinRequest(user0.address) + await competition.connect(user2).createJoinRequest(user1.address) + }) + + it("disable team members to create join requests", async () => { + await competition.connect(user0).registerTeam("1", code) + await competition.connect(user1).registerTeam("2", code) + await expect(competition.connect(user0).createJoinRequest(user1.address)).to.be.revertedWith("Team members are not allowed.") + }) + + it("allows team leaders to accept requests", async () => { + await competition.connect(user0).registerTeam("1", code) + await competition.connect(user1).createJoinRequest(user0.address) + try { + await competition.connect(user0).approveJoinRequest(user1.address) + + const members = await competition.getTeamMembers(user0.address) + console.log(members) + } catch (e) { + console.log(e) + } + }) + + it("disallow leaders to accept non existant join request", async () => { + await referralStorage.connect(user0).registerCode(code) + await competition.connect(user0).registerTeam("1", code) + await expect(competition.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") + }) + + it("disallow leaders to accept members that already joines another team", async () => { + await referralStorage.connect(user0).registerCode(code) + await competition.connect(user0).registerTeam("1", code) + await competition.connect(user1).registerTeam("2", code) + await competition.connect(user2).createJoinRequest(user0.address) + await competition.connect(user0).approveJoinRequest(user2.address) + await expect(competition.connect(user1).approveJoinRequest(user2.address)).to.be.revertedWith("This member already joined a team.") + }) }); From 3fc3d2eb94f40184dbdd9500b0d864772d4de2b6 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Sun, 28 Aug 2022 19:50:26 +0200 Subject: [PATCH 04/21] improve contract & tests --- contracts/competition/Competition.sol | 32 +++++++++++++---- test/competition/Competition.js | 49 +++++++++++++-------------- 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index a60c9bf8..f4ed12bd 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -23,7 +23,13 @@ contract Competition is Governable mapping(address => Team) public teams; mapping(string => bool) public teamNames; mapping(address => address) public membersToTeam; - mapping(address => mapping(address => bool)) public requests; + mapping(address => address) public requests; + + event TeamRegistered(address leader, string name, bytes32 referral); + event JoinRequestCreated(address member, address leader); + event JoinRequestCanceled(address member, address leader); + event JoinRequestApproved(address member, address leader); + event TimesChanged(uint start, uint end, uint registrationStart, uint registrationEnd); modifier registrationIsOpen() { @@ -49,6 +55,8 @@ contract Competition is Governable registrationStart = registrationStart_; registrationEnd = registrationEnd_; referralStorage = referralStorage_; + + emit TimesChanged(start, end, registrationStart, registrationEnd); } function registerTeam(string calldata name, bytes32 referral) external registrationIsOpen isNotMember { @@ -64,24 +72,36 @@ contract Competition is Governable leaders.push(msg.sender); teamNames[name] = true; membersToTeam[msg.sender] = msg.sender; + + emit TeamRegistered(msg.sender, name, referral); } function createJoinRequest(address leaderAddress) external registrationIsOpen isNotMember { require(membersToTeam[msg.sender] == address(0), "You can't join multiple teams."); require(teams[leaderAddress].leader != address(0), "The team does not exist."); - require(requests[leaderAddress][msg.sender] == false, "You already applied for this team."); + require(requests[msg.sender] == address(0), "You already have an active join request."); teams[leaderAddress].joinRequests.push(msg.sender); - requests[leaderAddress][msg.sender] = true; + requests[msg.sender] = leaderAddress; + + emit JoinRequestCreated(msg.sender, leaderAddress); } function approveJoinRequest(address memberAddress) external registrationIsOpen { - require(requests[msg.sender][memberAddress] == true, "This member did not apply."); + require(requests[memberAddress] == msg.sender, "This member did not apply."); require(membersToTeam[memberAddress] == address(0), "This member already joined a team."); - referralStorage.setTraderReferralCode(memberAddress, teams[msg.sender].referral); - teams[msg.sender].members.push(msg.sender); + // referralStorage.setTraderReferralCode(memberAddress, teams[msg.sender].referral); + teams[msg.sender].members.push(memberAddress); membersToTeam[memberAddress] = msg.sender; + requests[memberAddress] = address(0); + + emit JoinRequestApproved(memberAddress, msg.sender); + } + + function cancelJoinRequest(address leaderAddress) external registrationIsOpen { + require(requests[msg.sender] == leaderAddress, "You already have an active join request."); + requests[msg.sender] = address(0); } function getLeaders(uint start_, uint offset) external view returns (address[] memory) { diff --git a/test/competition/Competition.js b/test/competition/Competition.js index f31bed42..8be5502d 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -7,10 +7,6 @@ use(solidity) const { keccak256 } = ethers.utils -// Tier0 (5% discount, 5% rebate) = Tier {totalRebate = 1000, defaultTradersDiscountShare = 5000} -// Tier1 (12% discount, 8% rebate) = Tier {totalRebate = 2000, defaultTradersDiscountShare = 6000} -// Tier2 (12% discount, 15% rebate) = Tier {totalRebate = 2700, defaultTradersDiscountShare = 4444} -// for the last tier extra EsGMX incentives will be handled off-chain describe("Competition", function () { const provider = waffle.provider const [wallet, user0, user1, user2] = provider.getWallets() @@ -58,11 +54,7 @@ describe("Competition", function () { }) it("allows people to register teams in times", async () => { - try { - await competition.connect(user0).registerTeam("1", code) - } catch (e) { - console.log(e) - } + await competition.connect(user0).registerTeam("1", code) }) it("disable people to register multiple teams", async () => { @@ -80,10 +72,22 @@ describe("Competition", function () { }) it("allows people to create join requests", async () => { + await competition.connect(user0).registerTeam("1", code) + await competition.connect(user1).createJoinRequest(user0.address) + }) + + it("disable people to create multiple join requests", async () => { await competition.connect(user0).registerTeam("1", code) await competition.connect(user1).registerTeam("2", code) await competition.connect(user2).createJoinRequest(user0.address) - await competition.connect(user2).createJoinRequest(user1.address) + await expect(competition.connect(user2).createJoinRequest(user1.address)).to.be.revertedWith("You already have an active join request.") + }) + + it("allow people to cancel join requests", async () => { + await competition.connect(user0).registerTeam("1", code) + await competition.connect(user1).createJoinRequest(user0.address) + await competition.connect(user1).cancelJoinRequest(user0.address) + await expect(competition.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") }) it("disable team members to create join requests", async () => { @@ -95,28 +99,23 @@ describe("Competition", function () { it("allows team leaders to accept requests", async () => { await competition.connect(user0).registerTeam("1", code) await competition.connect(user1).createJoinRequest(user0.address) - try { - await competition.connect(user0).approveJoinRequest(user1.address) - + await competition.connect(user0).approveJoinRequest(user1.address) const members = await competition.getTeamMembers(user0.address) - console.log(members) - } catch (e) { - console.log(e) - } + expect(members).to.include(user1.address) }) it("disallow leaders to accept non existant join request", async () => { - await referralStorage.connect(user0).registerCode(code) await competition.connect(user0).registerTeam("1", code) await expect(competition.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") }) - it("disallow leaders to accept members that already joines another team", async () => { - await referralStorage.connect(user0).registerCode(code) - await competition.connect(user0).registerTeam("1", code) - await competition.connect(user1).registerTeam("2", code) - await competition.connect(user2).createJoinRequest(user0.address) - await competition.connect(user0).approveJoinRequest(user2.address) - await expect(competition.connect(user1).approveJoinRequest(user2.address)).to.be.revertedWith("This member already joined a team.") + it("disallow leaders to accept members before registration time", async () => { + await competition.connect(wallet).setRegistrationStart((await getBlockTime(provider)) + 10) + await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") + }) + + it("disallow leaders to accept members after registration time", async () => { + await competition.connect(wallet).setRegistrationEnd((await getBlockTime(provider)) - 10) + await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) }); From 09ccb845896b8024265b05338ec57832da208ce8 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Sun, 28 Aug 2022 19:59:55 +0200 Subject: [PATCH 05/21] allow leaders to kick members --- contracts/competition/Competition.sol | 12 ++++++++++++ test/competition/Competition.js | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index f4ed12bd..0453303a 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -104,6 +104,18 @@ contract Competition is Governable requests[msg.sender] = address(0); } + function removeMember(address memberAddress) external registrationIsOpen { + require(membersToTeam[memberAddress] == msg.sender, "This member is not in your team"); + membersToTeam[memberAddress] = address(0); + + for (uint i = 0; i < teams[msg.sender].members.length; i++) { + if (teams[msg.sender].members[i] == memberAddress) { + delete teams[msg.sender].members[i]; + break; + } + } + } + function getLeaders(uint start_, uint offset) external view returns (address[] memory) { address[] memory res; diff --git a/test/competition/Competition.js b/test/competition/Competition.js index 8be5502d..b8adba46 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -118,4 +118,15 @@ describe("Competition", function () { await competition.connect(wallet).setRegistrationEnd((await getBlockTime(provider)) - 10) await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) + + it("allow leaders to kick members", async () => { + await competition.connect(user0).registerTeam("1", code) + await competition.connect(user1).createJoinRequest(user0.address) + await competition.connect(user0).approveJoinRequest(user1.address) + let members = await competition.getTeamMembers(user0.address) + expect(members).to.include(user1.address) + await competition.connect(user0).removeMember(user1.address) + members = await competition.getTeamMembers(user0.address) + expect(members).to.not.include(user1.address) + }) }); From 0556575006a4129e49309881bbf3db7b3bc05556 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Sun, 28 Aug 2022 20:00:39 +0200 Subject: [PATCH 06/21] improve kick member test --- test/competition/Competition.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/competition/Competition.js b/test/competition/Competition.js index b8adba46..ada3df6e 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -128,5 +128,6 @@ describe("Competition", function () { await competition.connect(user0).removeMember(user1.address) members = await competition.getTeamMembers(user0.address) expect(members).to.not.include(user1.address) + await competition.connect(user1).createJoinRequest(user0.address) }) }); From e269d614a8c14d04918f9869e13788c9f0b28a51 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Mon, 29 Aug 2022 21:40:59 +0200 Subject: [PATCH 07/21] change cancel function --- contracts/competition/Competition.sol | 6 +++--- test/competition/Competition.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index 0453303a..17e6a602 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -27,7 +27,7 @@ contract Competition is Governable event TeamRegistered(address leader, string name, bytes32 referral); event JoinRequestCreated(address member, address leader); - event JoinRequestCanceled(address member, address leader); + event JoinRequestCanceled(address member); event JoinRequestApproved(address member, address leader); event TimesChanged(uint start, uint end, uint registrationStart, uint registrationEnd); @@ -99,9 +99,9 @@ contract Competition is Governable emit JoinRequestApproved(memberAddress, msg.sender); } - function cancelJoinRequest(address leaderAddress) external registrationIsOpen { - require(requests[msg.sender] == leaderAddress, "You already have an active join request."); + function cancelJoinRequest() external registrationIsOpen { requests[msg.sender] = address(0); + emit JoinRequestCanceled(msg.sender); } function removeMember(address memberAddress) external registrationIsOpen { diff --git a/test/competition/Competition.js b/test/competition/Competition.js index ada3df6e..6feb6fb8 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -86,7 +86,7 @@ describe("Competition", function () { it("allow people to cancel join requests", async () => { await competition.connect(user0).registerTeam("1", code) await competition.connect(user1).createJoinRequest(user0.address) - await competition.connect(user1).cancelJoinRequest(user0.address) + await competition.connect(user1).cancelJoinRequest() await expect(competition.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") }) From 3ca866d811cd955f011388fe1eea20b6a4ff5ad2 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Tue, 30 Aug 2022 09:23:05 +0200 Subject: [PATCH 08/21] delete registrationStart and registrationEnd variables (useless) --- contracts/competition/Competition.sol | 31 +++++++++------------------ test/competition/Competition.js | 24 ++++----------------- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index 17e6a602..56df0a1a 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -16,8 +16,6 @@ contract Competition is Governable uint public start; uint public end; - uint public registrationStart; - uint public registrationEnd; IReferralStorage public referralStorage; address[] public leaders; mapping(address => Team) public teams; @@ -29,16 +27,15 @@ contract Competition is Governable event JoinRequestCreated(address member, address leader); event JoinRequestCanceled(address member); event JoinRequestApproved(address member, address leader); - event TimesChanged(uint start, uint end, uint registrationStart, uint registrationEnd); + event MemberRemoved(address leader, address member); + event TimesChanged(uint start, uint end); - modifier registrationIsOpen() - { - require(block.timestamp >= registrationStart && block.timestamp < registrationEnd, "Registration is closed."); + modifier registrationIsOpen() { + require(block.timestamp < start, "Registration is closed."); _; } - modifier isNotMember() - { + modifier isNotMember() { require(membersToTeam[msg.sender] == address(0), "Team members are not allowed."); _; } @@ -46,17 +43,13 @@ contract Competition is Governable constructor( uint start_, uint end_, - uint registrationStart_, - uint registrationEnd_, IReferralStorage referralStorage_ ) public { start = start_; end = end_; - registrationStart = registrationStart_; - registrationEnd = registrationEnd_; referralStorage = referralStorage_; - emit TimesChanged(start, end, registrationStart, registrationEnd); + emit TimesChanged(start, end); } function registerTeam(string calldata name, bytes32 referral) external registrationIsOpen isNotMember { @@ -114,6 +107,8 @@ contract Competition is Governable break; } } + + emit MemberRemoved(msg.sender, memberAddress); } function getLeaders(uint start_, uint offset) external view returns (address[] memory) { @@ -150,17 +145,11 @@ contract Competition is Governable function setStart(uint start_) external onlyGov { start = start_; + emit TimesChanged(start, end); } function setEnd(uint end_) external onlyGov { end = end_; - } - - function setRegistrationStart(uint registrationStart_) external onlyGov { - registrationStart = registrationStart_; - } - - function setRegistrationEnd(uint registrationEnd_) external onlyGov { - registrationEnd = registrationEnd_; + emit TimesChanged(start, end); } } diff --git a/test/competition/Competition.js b/test/competition/Competition.js index 6feb6fb8..319f1ca0 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -19,10 +19,8 @@ describe("Competition", function () { referralStorage = await deployContract("ReferralStorage", []) competition = await deployContract("Competition", [ - ts + 60, // start - ts + 120, // end - ts - 60, // registrationStart - ts + 60, // registrationEnd + ts + 10, // start + ts + 60, // end referralStorage.address ]); @@ -32,24 +30,15 @@ describe("Competition", function () { it("allows owner to set times", async () => { await competition.connect(wallet).setStart(1) await competition.connect(wallet).setEnd(1) - await competition.connect(wallet).setRegistrationStart(1) - await competition.connect(wallet).setRegistrationEnd(1) }) it("disable non owners to set times", async () => { await expect(competition.connect(user0).setStart(1)).to.be.revertedWith("Governable: forbidden") await expect(competition.connect(user0).setEnd(1)).to.be.revertedWith("Governable: forbidden") - await expect(competition.connect(user0).setRegistrationStart(1)).to.be.revertedWith("Governable: forbidden") - await expect(competition.connect(user0).setRegistrationEnd(1)).to.be.revertedWith("Governable: forbidden") - }) - - it("disable people to register teams before registration time", async () => { - await competition.connect(wallet).setRegistrationStart((await getBlockTime(provider)) + 10) - await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) it("disable people to register teams after registration time", async () => { - await competition.connect(wallet).setRegistrationEnd((await getBlockTime(provider)) - 10) + await competition.connect(wallet).setStart((await getBlockTime(provider)) - 10) await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) @@ -109,13 +98,8 @@ describe("Competition", function () { await expect(competition.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") }) - it("disallow leaders to accept members before registration time", async () => { - await competition.connect(wallet).setRegistrationStart((await getBlockTime(provider)) + 10) - await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") - }) - it("disallow leaders to accept members after registration time", async () => { - await competition.connect(wallet).setRegistrationEnd((await getBlockTime(provider)) - 10) + await competition.connect(wallet).setStart((await getBlockTime(provider)) - 10) await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) From 641046bfaac41b92d188c1a5459dde7dd97d8a2c Mon Sep 17 00:00:00 2001 From: Morazzela Date: Tue, 30 Aug 2022 10:08:53 +0200 Subject: [PATCH 09/21] add max member --- contracts/competition/Competition.sol | 8 ++++++++ test/competition/Competition.js | 27 ++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index 56df0a1a..aea6268c 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -16,6 +16,7 @@ contract Competition is Governable uint public start; uint public end; + uint public maxMembersPerTeam; IReferralStorage public referralStorage; address[] public leaders; mapping(address => Team) public teams; @@ -43,10 +44,12 @@ contract Competition is Governable constructor( uint start_, uint end_, + uint maxMembersPerTeam_, IReferralStorage referralStorage_ ) public { start = start_; end = end_; + maxMembersPerTeam = maxMembersPerTeam_; referralStorage = referralStorage_; emit TimesChanged(start, end); @@ -83,6 +86,7 @@ contract Competition is Governable function approveJoinRequest(address memberAddress) external registrationIsOpen { require(requests[memberAddress] == msg.sender, "This member did not apply."); require(membersToTeam[memberAddress] == address(0), "This member already joined a team."); + require(teams[msg.sender].members.length < maxMembersPerTeam, "Team is full."); // referralStorage.setTraderReferralCode(memberAddress, teams[msg.sender].referral); teams[msg.sender].members.push(memberAddress); @@ -152,4 +156,8 @@ contract Competition is Governable end = end_; emit TimesChanged(start, end); } + + function setMaxMembersPerTeam(uint maxMembersPerTeam_) external onlyGov { + maxMembersPerTeam = maxMembersPerTeam_; + } } diff --git a/test/competition/Competition.js b/test/competition/Competition.js index 319f1ca0..031fca5a 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -9,7 +9,7 @@ const { keccak256 } = ethers.utils describe("Competition", function () { const provider = waffle.provider - const [wallet, user0, user1, user2] = provider.getWallets() + const [wallet, user0, user1, user2, user3] = provider.getWallets() let competition let referralStorage let code = keccak256("0xFF") @@ -21,6 +21,7 @@ describe("Competition", function () { competition = await deployContract("Competition", [ ts + 10, // start ts + 60, // end + 10, referralStorage.address ]); @@ -37,6 +38,14 @@ describe("Competition", function () { await expect(competition.connect(user0).setEnd(1)).to.be.revertedWith("Governable: forbidden") }) + it("allows owner to set max members per team", async () => { + await competition.setMaxMembersPerTeam(1); + }) + + it("disable non owners to set max members per team", async () => { + await expect(competition.connect(user0).setMaxMembersPerTeam(1)).to.be.revertedWith("Governable: forbidden") + }) + it("disable people to register teams after registration time", async () => { await competition.connect(wallet).setStart((await getBlockTime(provider)) - 10) await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") @@ -114,4 +123,20 @@ describe("Competition", function () { expect(members).to.not.include(user1.address) await competition.connect(user1).createJoinRequest(user0.address) }) + + it("disallow leader to accept join request if team is full", async () => { + await competition.connect(user0).registerTeam("1", code) + + await competition.connect(user1).createJoinRequest(user0.address) + await competition.connect(user0).approveJoinRequest(user1.address) + + await competition.connect(user2).createJoinRequest(user0.address) + await competition.connect(user0).approveJoinRequest(user2.address) + + try { + await competition.connect(wallet).setMaxMembersPerTeam(2); + await competition.connect(user3).createJoinRequest(user0.address) + await competition.connect(user0).approveJoinRequest(user3.address) + } catch (e) { console.log(e) } + }) }); From ad0949aba218ab2d6f42048318a5624f80476f18 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Tue, 30 Aug 2022 11:08:28 +0200 Subject: [PATCH 10/21] create competition details struct --- contracts/competition/Competition.sol | 47 ++++++++++++--------------- test/competition/Competition.js | 33 ++++++++----------- 2 files changed, 34 insertions(+), 46 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index aea6268c..5b0280c5 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -14,9 +14,13 @@ contract Competition is Governable address[] joinRequests; } - uint public start; - uint public end; - uint public maxMembersPerTeam; + struct CompetitionDetails { + uint start; + uint end; + uint maxTeamSize; + } + + CompetitionDetails public competitionDetails; IReferralStorage public referralStorage; address[] public leaders; mapping(address => Team) public teams; @@ -29,10 +33,10 @@ contract Competition is Governable event JoinRequestCanceled(address member); event JoinRequestApproved(address member, address leader); event MemberRemoved(address leader, address member); - event TimesChanged(uint start, uint end); + event CompetitionDetailsChanged(uint start, uint end, uint maxTeamSize); modifier registrationIsOpen() { - require(block.timestamp < start, "Registration is closed."); + require(block.timestamp < competitionDetails.start, "Registration is closed."); _; } @@ -42,17 +46,14 @@ contract Competition is Governable } constructor( - uint start_, - uint end_, - uint maxMembersPerTeam_, + uint start, + uint end, + uint maxTeamSize, IReferralStorage referralStorage_ ) public { - start = start_; - end = end_; - maxMembersPerTeam = maxMembersPerTeam_; + competitionDetails = CompetitionDetails(start, end, maxTeamSize); referralStorage = referralStorage_; - - emit TimesChanged(start, end); + emit CompetitionDetailsChanged(start, end, maxTeamSize); } function registerTeam(string calldata name, bytes32 referral) external registrationIsOpen isNotMember { @@ -86,7 +87,7 @@ contract Competition is Governable function approveJoinRequest(address memberAddress) external registrationIsOpen { require(requests[memberAddress] == msg.sender, "This member did not apply."); require(membersToTeam[memberAddress] == address(0), "This member already joined a team."); - require(teams[msg.sender].members.length < maxMembersPerTeam, "Team is full."); + require(teams[msg.sender].members.length < competitionDetails.maxTeamSize, "Team is full."); // referralStorage.setTraderReferralCode(memberAddress, teams[msg.sender].referral); teams[msg.sender].members.push(memberAddress); @@ -147,17 +148,11 @@ contract Competition is Governable return res; } - function setStart(uint start_) external onlyGov { - start = start_; - emit TimesChanged(start, end); - } - - function setEnd(uint end_) external onlyGov { - end = end_; - emit TimesChanged(start, end); - } - - function setMaxMembersPerTeam(uint maxMembersPerTeam_) external onlyGov { - maxMembersPerTeam = maxMembersPerTeam_; + function setCompetitionDetails(uint start, uint end, uint maxTeamSize) external onlyGov + { + competitionDetails.start = start; + competitionDetails.end = end; + competitionDetails.maxTeamSize = maxTeamSize; + emit CompetitionDetailsChanged(start, end, maxTeamSize); } } diff --git a/test/competition/Competition.js b/test/competition/Competition.js index 031fca5a..6b431aeb 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -28,26 +28,19 @@ describe("Competition", function () { await referralStorage.registerCode(code) }) - it("allows owner to set times", async () => { - await competition.connect(wallet).setStart(1) - await competition.connect(wallet).setEnd(1) - }) - - it("disable non owners to set times", async () => { - await expect(competition.connect(user0).setStart(1)).to.be.revertedWith("Governable: forbidden") - await expect(competition.connect(user0).setEnd(1)).to.be.revertedWith("Governable: forbidden") - }) - - it("allows owner to set max members per team", async () => { - await competition.setMaxMembersPerTeam(1); + it("allows owner to set competition details", async () => { + const ts = await getBlockTime(provider) + await competition.connect(wallet).setCompetitionDetails(ts, ts + 10, 10) }) - it("disable non owners to set max members per team", async () => { - await expect(competition.connect(user0).setMaxMembersPerTeam(1)).to.be.revertedWith("Governable: forbidden") + it("disable non owners to set competition details", async () => { + const ts = await getBlockTime(provider) + await expect(competition.connect(user0).setCompetitionDetails(ts, ts + 10, 10)).to.be.revertedWith("Governable: forbidden") }) it("disable people to register teams after registration time", async () => { - await competition.connect(wallet).setStart((await getBlockTime(provider)) - 10) + const ts = await getBlockTime(provider) + await competition.connect(wallet).setCompetitionDetails(ts - 10, ts + 60, 10) await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) @@ -108,7 +101,8 @@ describe("Competition", function () { }) it("disallow leaders to accept members after registration time", async () => { - await competition.connect(wallet).setStart((await getBlockTime(provider)) - 10) + const ts = await getBlockTime(provider) + await competition.connect(wallet).setCompetitionDetails(ts - 10, ts + 60, 10) await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) @@ -133,10 +127,9 @@ describe("Competition", function () { await competition.connect(user2).createJoinRequest(user0.address) await competition.connect(user0).approveJoinRequest(user2.address) - try { - await competition.connect(wallet).setMaxMembersPerTeam(2); + const ts = await getBlockTime(provider) + await competition.connect(wallet).setCompetitionDetails(ts + 10, ts + 60, 2) await competition.connect(user3).createJoinRequest(user0.address) - await competition.connect(user0).approveJoinRequest(user3.address) - } catch (e) { console.log(e) } + await expect(competition.connect(user0).approveJoinRequest(user3.address)).to.be.revertedWith("Team is full.") }) }); From 04bf6eca78f4626bcb44c6c8c1832d2782d3c647 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Tue, 30 Aug 2022 15:15:44 +0200 Subject: [PATCH 11/21] allow multiple competitions --- contracts/competition/Competition.sol | 80 +++++++++-------- test/competition/Competition.js | 123 +++++++++++++------------- test/shared/utilities.js | 7 +- 3 files changed, 110 insertions(+), 100 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index 5b0280c5..8dd914eb 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -14,13 +14,14 @@ contract Competition is Governable address[] joinRequests; } - struct CompetitionDetails { + struct Competition { uint start; uint end; - uint maxTeamSize; } - CompetitionDetails public competitionDetails; + uint public nextCompetitionIndex = 0; + Competition[] public competitions; + uint public maxTeamSize = 10; IReferralStorage public referralStorage; address[] public leaders; mapping(address => Team) public teams; @@ -33,10 +34,19 @@ contract Competition is Governable event JoinRequestCanceled(address member); event JoinRequestApproved(address member, address leader); event MemberRemoved(address leader, address member); - event CompetitionDetailsChanged(uint start, uint end, uint maxTeamSize); + event CompetitionCreated(uint index, uint start, uint end); + event CompetitionUpdated(uint index, uint start, uint end); modifier registrationIsOpen() { - require(block.timestamp < competitionDetails.start, "Registration is closed."); + for (uint i = 0; i < competitions.length; i++) { + uint start = competitions[i].start; + uint end = competitions[i].end; + + if (block.timestamp >= start && block.timestamp < end) { + require(false, "Registration is closed."); + } + } + _; } @@ -45,15 +55,30 @@ contract Competition is Governable _; } - constructor( - uint start, - uint end, - uint maxTeamSize, - IReferralStorage referralStorage_ - ) public { - competitionDetails = CompetitionDetails(start, end, maxTeamSize); - referralStorage = referralStorage_; - emit CompetitionDetailsChanged(start, end, maxTeamSize); + constructor(IReferralStorage _referralStorage) public { + referralStorage = _referralStorage; + } + + function createCompetition(uint start, uint end) external onlyGov returns (uint) { + require(start > block.timestamp, "Start time must be in the future."); + require(end > start, "End time must be greater than start time."); + + competitions.push(Competition(start, end)); + + emit CompetitionCreated(nextCompetitionIndex, start, end); + + return nextCompetitionIndex++; + } + + function updateCompetition(uint index, uint start, uint end) external onlyGov { + competitions[index].start = start; + competitions[index].end = end; + + emit CompetitionUpdated(index, start, end); + } + + function setMaxTeamSize(uint _maxTeamSize) external onlyGov { + maxTeamSize = _maxTeamSize; } function registerTeam(string calldata name, bytes32 referral) external registrationIsOpen isNotMember { @@ -87,7 +112,7 @@ contract Competition is Governable function approveJoinRequest(address memberAddress) external registrationIsOpen { require(requests[memberAddress] == msg.sender, "This member did not apply."); require(membersToTeam[memberAddress] == address(0), "This member already joined a team."); - require(teams[msg.sender].members.length < competitionDetails.maxTeamSize, "Team is full."); + require(teams[msg.sender].members.length < maxTeamSize, "Team is full."); // referralStorage.setTraderReferralCode(memberAddress, teams[msg.sender].referral); teams[msg.sender].members.push(memberAddress); @@ -116,10 +141,10 @@ contract Competition is Governable emit MemberRemoved(msg.sender, memberAddress); } - function getLeaders(uint start_, uint offset) external view returns (address[] memory) { + function getLeaders(uint start, uint offset) external view returns (address[] memory) { address[] memory res; - for (uint i = start_; i < leaders.length && i < start_ + offset; i++) { + for (uint i = start; i < leaders.length && i < start + offset; i++) { res[i] = leaders[i]; } @@ -134,25 +159,4 @@ contract Competition is Governable function getTeamMembers(address leaderAddr) external view returns (address[] memory) { return teams[leaderAddr].members; } - - function getTeamJoinRequests(address leaderAddr) external view returns (address[] memory) { - address[] memory res; - - for (uint i = 0; i < teams[leaderAddr].joinRequests.length; i++) { - address jr = teams[leaderAddr].joinRequests[i]; - if (membersToTeam[jr] == address(0)) { - res[i] = jr; - } - } - - return res; - } - - function setCompetitionDetails(uint start, uint end, uint maxTeamSize) external onlyGov - { - competitionDetails.start = start; - competitionDetails.end = end; - competitionDetails.maxTeamSize = maxTeamSize; - emit CompetitionDetailsChanged(start, end, maxTeamSize); - } } diff --git a/test/competition/Competition.js b/test/competition/Competition.js index 6b431aeb..972a9a97 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -1,7 +1,7 @@ const { expect, use } = require("chai") const { solidity } = require("ethereum-waffle") const { deployContract } = require("../shared/fixtures") -const { getBlockTime } = require("../shared/utilities") +const { getBlockTime, sleep } = require("../shared/utilities") use(solidity) @@ -10,126 +10,127 @@ const { keccak256 } = ethers.utils describe("Competition", function () { const provider = waffle.provider const [wallet, user0, user1, user2, user3] = provider.getWallets() - let competition + let contract + let ts let referralStorage let code = keccak256("0xFF") beforeEach(async () => { - const ts = await getBlockTime(provider) - + ts = await getBlockTime(provider) referralStorage = await deployContract("ReferralStorage", []) - competition = await deployContract("Competition", [ - ts + 10, // start - ts + 60, // end - 10, - referralStorage.address - ]); - + contract = await deployContract("Competition", [referralStorage.address]); await referralStorage.registerCode(code) }) - it("allows owner to set competition details", async () => { - const ts = await getBlockTime(provider) - await competition.connect(wallet).setCompetitionDetails(ts, ts + 10, 10) + it("allows owner to create competition", async () => { + await contract.connect(wallet).createCompetition(ts + 10, ts + 20) }) it("disable non owners to set competition details", async () => { - const ts = await getBlockTime(provider) - await expect(competition.connect(user0).setCompetitionDetails(ts, ts + 10, 10)).to.be.revertedWith("Governable: forbidden") + await expect(contract.connect(user0).createCompetition(ts + 10, ts + 20)).to.be.revertedWith("Governable: forbidden") }) it("disable people to register teams after registration time", async () => { const ts = await getBlockTime(provider) - await competition.connect(wallet).setCompetitionDetails(ts - 10, ts + 60, 10) - await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") + await contract.connect(wallet).createCompetition(ts + 2, ts + 60) + await sleep(2000); + await expect(contract.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) it("allows people to register teams in times", async () => { - await competition.connect(user0).registerTeam("1", code) + await contract.connect(user0).registerTeam("1", code) }) it("disable people to register multiple teams", async () => { - await competition.connect(user0).registerTeam("1", code) - await expect(competition.connect(user0).registerTeam("2", code)).to.be.revertedWith("Team members are not allowed.") + await contract.connect(user0).registerTeam("1", code) + await expect(contract.connect(user0).registerTeam("2", code)).to.be.revertedWith("Team members are not allowed.") }) it("disable people to register a team with non existing referral code", async () => { - await expect(competition.connect(user0).registerTeam("1", keccak256("0xFE"))).to.be.revertedWith("Referral code does not exist.") + await expect(contract.connect(user0).registerTeam("1", keccak256("0xFE"))).to.be.revertedWith("Referral code does not exist.") }) it("disable multiple teams with the same name", async () => { - await competition.connect(user0).registerTeam("1", code) - await expect(competition.connect(user1).registerTeam("1", code)).to.be.revertedWith("Team name already registered.") + await contract.connect(user0).registerTeam("1", code) + await expect(contract.connect(user1).registerTeam("1", code)).to.be.revertedWith("Team name already registered.") }) it("allows people to create join requests", async () => { - await competition.connect(user0).registerTeam("1", code) - await competition.connect(user1).createJoinRequest(user0.address) + await contract.connect(user0).registerTeam("1", code) + await contract.connect(user1).createJoinRequest(user0.address) }) it("disable people to create multiple join requests", async () => { - await competition.connect(user0).registerTeam("1", code) - await competition.connect(user1).registerTeam("2", code) - await competition.connect(user2).createJoinRequest(user0.address) - await expect(competition.connect(user2).createJoinRequest(user1.address)).to.be.revertedWith("You already have an active join request.") + await contract.connect(user0).registerTeam("1", code) + await contract.connect(user1).registerTeam("2", code) + await contract.connect(user2).createJoinRequest(user0.address) + await expect(contract.connect(user2).createJoinRequest(user1.address)).to.be.revertedWith("You already have an active join request.") }) it("allow people to cancel join requests", async () => { - await competition.connect(user0).registerTeam("1", code) - await competition.connect(user1).createJoinRequest(user0.address) - await competition.connect(user1).cancelJoinRequest() - await expect(competition.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") + await contract.connect(user0).registerTeam("1", code) + await contract.connect(user1).createJoinRequest(user0.address) + await contract.connect(user1).cancelJoinRequest() + await expect(contract.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") }) it("disable team members to create join requests", async () => { - await competition.connect(user0).registerTeam("1", code) - await competition.connect(user1).registerTeam("2", code) - await expect(competition.connect(user0).createJoinRequest(user1.address)).to.be.revertedWith("Team members are not allowed.") + await contract.connect(user0).registerTeam("1", code) + await contract.connect(user1).registerTeam("2", code) + await expect(contract.connect(user0).createJoinRequest(user1.address)).to.be.revertedWith("Team members are not allowed.") }) it("allows team leaders to accept requests", async () => { - await competition.connect(user0).registerTeam("1", code) - await competition.connect(user1).createJoinRequest(user0.address) - await competition.connect(user0).approveJoinRequest(user1.address) - const members = await competition.getTeamMembers(user0.address) + await contract.connect(user0).registerTeam("1", code) + await contract.connect(user1).createJoinRequest(user0.address) + await contract.connect(user0).approveJoinRequest(user1.address) + const members = await contract.getTeamMembers(user0.address) expect(members).to.include(user1.address) }) it("disallow leaders to accept non existant join request", async () => { - await competition.connect(user0).registerTeam("1", code) - await expect(competition.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") + await contract.connect(user0).registerTeam("1", code) + await expect(contract.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") }) it("disallow leaders to accept members after registration time", async () => { const ts = await getBlockTime(provider) - await competition.connect(wallet).setCompetitionDetails(ts - 10, ts + 60, 10) - await expect(competition.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") + await contract.connect(wallet).createCompetition(ts + 2, ts + 10) + await sleep(2000) + await expect(contract.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") }) it("allow leaders to kick members", async () => { - await competition.connect(user0).registerTeam("1", code) - await competition.connect(user1).createJoinRequest(user0.address) - await competition.connect(user0).approveJoinRequest(user1.address) - let members = await competition.getTeamMembers(user0.address) + await contract.connect(user0).registerTeam("1", code) + await contract.connect(user1).createJoinRequest(user0.address) + await contract.connect(user0).approveJoinRequest(user1.address) + let members = await contract.getTeamMembers(user0.address) expect(members).to.include(user1.address) - await competition.connect(user0).removeMember(user1.address) - members = await competition.getTeamMembers(user0.address) + await contract.connect(user0).removeMember(user1.address) + members = await contract.getTeamMembers(user0.address) expect(members).to.not.include(user1.address) - await competition.connect(user1).createJoinRequest(user0.address) + await contract.connect(user1).createJoinRequest(user0.address) + }) + + it("allow owner to change team size", async () => { + await contract.connect(wallet).setMaxTeamSize(2) + }) + + it("disallow non owners to change team size", async () => { + await expect(contract.connect(user0).setMaxTeamSize(2)).to.be.revertedWith("Governable: forbidden") }) it("disallow leader to accept join request if team is full", async () => { - await competition.connect(user0).registerTeam("1", code) + await contract.connect(user0).registerTeam("1", code) - await competition.connect(user1).createJoinRequest(user0.address) - await competition.connect(user0).approveJoinRequest(user1.address) + await contract.connect(user1).createJoinRequest(user0.address) + await contract.connect(user0).approveJoinRequest(user1.address) - await competition.connect(user2).createJoinRequest(user0.address) - await competition.connect(user0).approveJoinRequest(user2.address) + await contract.connect(user2).createJoinRequest(user0.address) + await contract.connect(user0).approveJoinRequest(user2.address) - const ts = await getBlockTime(provider) - await competition.connect(wallet).setCompetitionDetails(ts + 10, ts + 60, 2) - await competition.connect(user3).createJoinRequest(user0.address) - await expect(competition.connect(user0).approveJoinRequest(user3.address)).to.be.revertedWith("Team is full.") + await contract.connect(wallet).setMaxTeamSize(2) + await contract.connect(user3).createJoinRequest(user0.address) + await expect(contract.connect(user0).approveJoinRequest(user3.address)).to.be.revertedWith("Team is full.") }) }); diff --git a/test/shared/utilities.js b/test/shared/utilities.js index d97ad376..e443ee87 100644 --- a/test/shared/utilities.js +++ b/test/shared/utilities.js @@ -115,6 +115,10 @@ function getPriceBits(prices) { return priceBits.toString() } +async function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} + module.exports = { newWallet, maxUint256, @@ -129,5 +133,6 @@ module.exports = { getTxnBalances, print, getPriceBitArray, - getPriceBits + getPriceBits, + sleep } From a7ce0d03768c8375d140536d564faf1ded7be65a Mon Sep 17 00:00:00 2001 From: Morazzela Date: Tue, 30 Aug 2022 15:20:59 +0200 Subject: [PATCH 12/21] fix last commit --- contracts/competition/Competition.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index 8dd914eb..ae043e6c 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -59,7 +59,7 @@ contract Competition is Governable referralStorage = _referralStorage; } - function createCompetition(uint start, uint end) external onlyGov returns (uint) { + function createCompetition(uint start, uint end) external onlyGov { require(start > block.timestamp, "Start time must be in the future."); require(end > start, "End time must be greater than start time."); @@ -67,7 +67,7 @@ contract Competition is Governable emit CompetitionCreated(nextCompetitionIndex, start, end); - return nextCompetitionIndex++; + nextCompetitionIndex++; } function updateCompetition(uint index, uint start, uint end) external onlyGov { From 404bc002da75e3f4f1417aeb565cb1992541f55f Mon Sep 17 00:00:00 2001 From: Morazzela Date: Tue, 30 Aug 2022 19:06:22 +0200 Subject: [PATCH 13/21] change entire logic --- contracts/competition/Competition.sol | 171 +++++++++++++------------- test/competition/Competition.js | 129 +++++++++++-------- 2 files changed, 165 insertions(+), 135 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index ae043e6c..24767895 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -9,49 +9,44 @@ contract Competition is Governable struct Team { address leader; string name; - bytes32 referral; + bytes32 referralCode; address[] members; - address[] joinRequests; } struct Competition { uint start; uint end; + uint maxTeamSize; + mapping(address => Team) teams; + mapping(string => bool) teamNames; + mapping(address => address) memberTeams; + mapping(address => address) joinRequests; } uint public nextCompetitionIndex = 0; - Competition[] public competitions; - uint public maxTeamSize = 10; + mapping(uint => Competition) public competitions; IReferralStorage public referralStorage; - address[] public leaders; - mapping(address => Team) public teams; - mapping(string => bool) public teamNames; - mapping(address => address) public membersToTeam; - mapping(address => address) public requests; - - event TeamRegistered(address leader, string name, bytes32 referral); - event JoinRequestCreated(address member, address leader); - event JoinRequestCanceled(address member); - event JoinRequestApproved(address member, address leader); - event MemberRemoved(address leader, address member); - event CompetitionCreated(uint index, uint start, uint end); - event CompetitionUpdated(uint index, uint start, uint end); - - modifier registrationIsOpen() { - for (uint i = 0; i < competitions.length; i++) { - uint start = competitions[i].start; - uint end = competitions[i].end; - - if (block.timestamp >= start && block.timestamp < end) { - require(false, "Registration is closed."); - } - } + event TeamCreated(uint index, address leader, string name, bytes32 referral); + event JoinRequestCreated(uint index, address member, address leader); + event JoinRequestCanceled(uint index, address member); + event JoinRequestApproved(uint index, address member, address leader); + event MemberRemoved(uint index, address leader, address member); + event CompetitionCreated(uint index, uint start, uint end, uint maxTeamSize); + event CompetitionUpdated(uint index, uint start, uint end, uint maxTeamSize); + + modifier registrationIsOpen(uint competitionIndex) { + require(competitions[competitionIndex].start > block.timestamp, "Registration is closed."); _; } - modifier isNotMember() { - require(membersToTeam[msg.sender] == address(0), "Team members are not allowed."); + modifier isNotMember(uint competitionIndex) { + require(competitions[competitionIndex].memberTeams[msg.sender] == address(0), "Team members are not allowed."); + _; + } + + modifier competitionExists(uint index) { + require(competitions[index].start > 0, "The competition does not exist."); _; } @@ -59,104 +54,112 @@ contract Competition is Governable referralStorage = _referralStorage; } - function createCompetition(uint start, uint end) external onlyGov { + function createCompetition(uint start, uint end, uint maxTeamSize) external onlyGov { require(start > block.timestamp, "Start time must be in the future."); require(end > start, "End time must be greater than start time."); - competitions.push(Competition(start, end)); + competitions[nextCompetitionIndex] = Competition(start, end, maxTeamSize); - emit CompetitionCreated(nextCompetitionIndex, start, end); + emit CompetitionCreated(nextCompetitionIndex, start, end, maxTeamSize); nextCompetitionIndex++; } - function updateCompetition(uint index, uint start, uint end) external onlyGov { + function updateCompetition(uint index, uint start, uint end, uint maxTeamSize) external onlyGov competitionExists(index) { competitions[index].start = start; competitions[index].end = end; + competitions[index].maxTeamSize = maxTeamSize; - emit CompetitionUpdated(index, start, end); + emit CompetitionUpdated(index, start, end, maxTeamSize); } - function setMaxTeamSize(uint _maxTeamSize) external onlyGov { - maxTeamSize = _maxTeamSize; - } + function createTeam(uint competitionIndex, string calldata name, bytes32 referralCode) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) { + Competition storage competition = competitions[competitionIndex]; - function registerTeam(string calldata name, bytes32 referral) external registrationIsOpen isNotMember { - require(referralStorage.codeOwners(referral) != address(0), "Referral code does not exist."); - require(teamNames[name] == false, "Team name already registered."); + require(referralStorage.codeOwners(referralCode) != address(0), "Referral code does not exist."); + require(competition.teamNames[name] == false, "Team name already registered."); - Team storage team = teams[msg.sender]; + Team storage team = competition.teams[msg.sender]; team.leader = msg.sender; team.name = name; - team.referral = referral; + team.referralCode = referralCode; team.members.push(msg.sender); - leaders.push(msg.sender); - teamNames[name] = true; - membersToTeam[msg.sender] = msg.sender; + competition.teamNames[name] = true; + competition.memberTeams[msg.sender] = msg.sender; - emit TeamRegistered(msg.sender, name, referral); + emit TeamCreated(competitionIndex, msg.sender, name, referralCode); } - function createJoinRequest(address leaderAddress) external registrationIsOpen isNotMember { - require(membersToTeam[msg.sender] == address(0), "You can't join multiple teams."); - require(teams[leaderAddress].leader != address(0), "The team does not exist."); - require(requests[msg.sender] == address(0), "You already have an active join request."); + function createJoinRequest(uint competitionIndex, address leaderAddress) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) { + Competition storage competition = competitions[competitionIndex]; - teams[leaderAddress].joinRequests.push(msg.sender); - requests[msg.sender] = leaderAddress; + require(competition.memberTeams[msg.sender] == address(0), "You can't join multiple teams."); + require(competition.teams[leaderAddress].leader != address(0), "The team does not exist."); - emit JoinRequestCreated(msg.sender, leaderAddress); + competition.joinRequests[msg.sender] = leaderAddress; + + emit JoinRequestCreated(competitionIndex, msg.sender, leaderAddress); } - function approveJoinRequest(address memberAddress) external registrationIsOpen { - require(requests[memberAddress] == msg.sender, "This member did not apply."); - require(membersToTeam[memberAddress] == address(0), "This member already joined a team."); - require(teams[msg.sender].members.length < maxTeamSize, "Team is full."); + function approveJoinRequest(uint competitionIndex, address memberAddress) external registrationIsOpen(competitionIndex) { + Competition storage competition = competitions[competitionIndex]; + + require(competition.joinRequests[memberAddress] == msg.sender, "This member did not apply."); + require(competition.memberTeams[memberAddress] == address(0), "This member already joined a team."); + require(competition.teams[msg.sender].members.length < competition.maxTeamSize, "Team is full."); // referralStorage.setTraderReferralCode(memberAddress, teams[msg.sender].referral); - teams[msg.sender].members.push(memberAddress); - membersToTeam[memberAddress] = msg.sender; - requests[memberAddress] = address(0); + competition.teams[msg.sender].members.push(memberAddress); + competition.memberTeams[memberAddress] = msg.sender; + competition.joinRequests[memberAddress] = address(0); - emit JoinRequestApproved(memberAddress, msg.sender); + emit JoinRequestApproved(competitionIndex, memberAddress, msg.sender); } - function cancelJoinRequest() external registrationIsOpen { - requests[msg.sender] = address(0); - emit JoinRequestCanceled(msg.sender); + function cancelJoinRequest(uint competitionIndex) external registrationIsOpen(competitionIndex) { + competitions[competitionIndex].joinRequests[msg.sender] = address(0); + emit JoinRequestCanceled(competitionIndex, msg.sender); } - function removeMember(address memberAddress) external registrationIsOpen { - require(membersToTeam[memberAddress] == msg.sender, "This member is not in your team"); - membersToTeam[memberAddress] = address(0); + function removeMember(uint competitionIndex, address memberAddress) external registrationIsOpen(competitionIndex) { + Competition storage competition = competitions[competitionIndex]; - for (uint i = 0; i < teams[msg.sender].members.length; i++) { - if (teams[msg.sender].members[i] == memberAddress) { - delete teams[msg.sender].members[i]; + require(competition.memberTeams[memberAddress] == msg.sender, "This member is not in your team"); + + for (uint i = 0; i < competition.teams[msg.sender].members.length; i++) { + if (competition.teams[msg.sender].members[i] == memberAddress) { + delete competition.teams[msg.sender].members[i]; break; } } - emit MemberRemoved(msg.sender, memberAddress); - } - - function getLeaders(uint start, uint offset) external view returns (address[] memory) { - address[] memory res; + competition.memberTeams[memberAddress] = address(0); - for (uint i = start; i < leaders.length && i < start + offset; i++) { - res[i] = leaders[i]; - } + emit MemberRemoved(competitionIndex, msg.sender, memberAddress); + } - return res; + function getCompetition(uint index) external view returns (uint start, uint end, uint maxTeamSize) { + return ( + competitions[index].start, + competitions[index].end, + competitions[index].maxTeamSize + ); } - function getTeam(address leaderAddr) external view returns (address leader, string memory name, bytes32 referral) { - Team memory team = teams[leaderAddr]; - return (team.leader, team.name, team.referral); + function getTeam(uint competitionIndex, address leaderAddr) external view returns (address leader, string memory name, bytes32 referralCode) { + Team memory team = competitions[competitionIndex].teams[leaderAddr]; + return (team.leader, team.name, team.referralCode); } - function getTeamMembers(address leaderAddr) external view returns (address[] memory) { - return teams[leaderAddr].members; + function getTeamMembers(uint competitionIndex, address leaderAddr, uint start, uint offset) external view returns (address[] memory members) { + address[] memory members = competitions[competitionIndex].teams[leaderAddr].members; + address[] memory result = new address[](offset); + + for (uint i = start; i < start + offset && i < members.length; i++) { + result[i] = members[i]; + } + + return result; } } diff --git a/test/competition/Competition.js b/test/competition/Competition.js index 972a9a97..7070908b 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -1,5 +1,7 @@ +const { ADDRESS_ZERO } = require("@uniswap/v3-sdk") const { expect, use } = require("chai") const { solidity } = require("ethereum-waffle") +const { ethers } = require("hardhat") const { deployContract } = require("../shared/fixtures") const { getBlockTime, sleep } = require("../shared/utilities") @@ -15,122 +17,147 @@ describe("Competition", function () { let referralStorage let code = keccak256("0xFF") + async function getTeamMembers(index, addr) + { + let start = 0 + const offset = 100 + const result = [] + + while (true) { + let res = await contract.getTeamMembers(index, addr, start, offset) + res = res.filter(addr => addr !== ADDRESS_ZERO) + + res.forEach(r => { + result.push(r) + }) + + if (res.length < offset) { + break; + } + } + + return result + } + beforeEach(async () => { ts = await getBlockTime(provider) referralStorage = await deployContract("ReferralStorage", []) contract = await deployContract("Competition", [referralStorage.address]); await referralStorage.registerCode(code) + await contract.createCompetition(ts + 10, ts + 20, 10) }) it("allows owner to create competition", async () => { - await contract.connect(wallet).createCompetition(ts + 10, ts + 20) + await contract.connect(wallet).createCompetition(ts + 10, ts + 20, 10) }) it("disable non owners to set competition details", async () => { - await expect(contract.connect(user0).createCompetition(ts + 10, ts + 20)).to.be.revertedWith("Governable: forbidden") + await expect(contract.connect(user0).createCompetition(ts + 10, ts + 20, 10)).to.be.revertedWith("Governable: forbidden") }) it("disable people to register teams after registration time", async () => { const ts = await getBlockTime(provider) - await contract.connect(wallet).createCompetition(ts + 2, ts + 60) + await contract.connect(wallet).createCompetition(ts + 2, ts + 60, 10) await sleep(2000); - await expect(contract.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") + await expect(contract.connect(user0).createTeam(1, "1", code)).to.be.revertedWith("Registration is closed.") }) it("allows people to register teams in times", async () => { - await contract.connect(user0).registerTeam("1", code) + await contract.connect(user0).createTeam(0, "1", code) }) it("disable people to register multiple teams", async () => { - await contract.connect(user0).registerTeam("1", code) - await expect(contract.connect(user0).registerTeam("2", code)).to.be.revertedWith("Team members are not allowed.") + await contract.connect(user0).createTeam(0, "1", code) + await expect(contract.connect(user0).createTeam(0, "2", code)).to.be.revertedWith("Team members are not allowed.") }) it("disable people to register a team with non existing referral code", async () => { - await expect(contract.connect(user0).registerTeam("1", keccak256("0xFE"))).to.be.revertedWith("Referral code does not exist.") + await expect(contract.connect(user0).createTeam(0, "1", keccak256("0xFE"))).to.be.revertedWith("Referral code does not exist.") }) it("disable multiple teams with the same name", async () => { - await contract.connect(user0).registerTeam("1", code) - await expect(contract.connect(user1).registerTeam("1", code)).to.be.revertedWith("Team name already registered.") + await contract.connect(user0).createTeam(0, "1", code) + await expect(contract.connect(user1).createTeam(0, "1", code)).to.be.revertedWith("Team name already registered.") }) it("allows people to create join requests", async () => { - await contract.connect(user0).registerTeam("1", code) - await contract.connect(user1).createJoinRequest(user0.address) + await contract.connect(user0).createTeam(0, "1", code) + await contract.connect(user1).createJoinRequest(0, user0.address) }) - it("disable people to create multiple join requests", async () => { - await contract.connect(user0).registerTeam("1", code) - await contract.connect(user1).registerTeam("2", code) - await contract.connect(user2).createJoinRequest(user0.address) - await expect(contract.connect(user2).createJoinRequest(user1.address)).to.be.revertedWith("You already have an active join request.") + it("allows people to replace join requests", async () => { + await contract.connect(user0).createTeam(0, "1", code) + await contract.connect(user1).createTeam(0, "2", code) + await contract.connect(user2).createJoinRequest(0, user0.address) + await contract.connect(user2).createJoinRequest(0, user1.address) }) it("allow people to cancel join requests", async () => { - await contract.connect(user0).registerTeam("1", code) - await contract.connect(user1).createJoinRequest(user0.address) - await contract.connect(user1).cancelJoinRequest() - await expect(contract.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") + await contract.connect(user0).createTeam(0, "1", code) + await contract.connect(user1).createJoinRequest(0, user0.address) + await contract.connect(user1).cancelJoinRequest(0) + await expect(contract.connect(user0).approveJoinRequest(0, user1.address)).to.be.revertedWith("This member did not apply.") }) it("disable team members to create join requests", async () => { - await contract.connect(user0).registerTeam("1", code) - await contract.connect(user1).registerTeam("2", code) - await expect(contract.connect(user0).createJoinRequest(user1.address)).to.be.revertedWith("Team members are not allowed.") + await contract.connect(user0).createTeam(0, "1", code) + await contract.connect(user1).createTeam(0, "2", code) + await expect(contract.connect(user0).createJoinRequest(0, user1.address)).to.be.revertedWith("Team members are not allowed.") }) it("allows team leaders to accept requests", async () => { - await contract.connect(user0).registerTeam("1", code) - await contract.connect(user1).createJoinRequest(user0.address) - await contract.connect(user0).approveJoinRequest(user1.address) - const members = await contract.getTeamMembers(user0.address) + await contract.connect(user0).createTeam(0, "1", code) + await contract.connect(user1).createJoinRequest(0, user0.address) + await contract.connect(user0).approveJoinRequest(0, user1.address) + const members = await getTeamMembers(0, user0.address) expect(members).to.include(user1.address) }) it("disallow leaders to accept non existant join request", async () => { - await contract.connect(user0).registerTeam("1", code) - await expect(contract.connect(user0).approveJoinRequest(user1.address)).to.be.revertedWith("This member did not apply.") + await contract.connect(user0).createTeam(0, "1", code) + await expect(contract.connect(user0).approveJoinRequest(0, user1.address)).to.be.revertedWith("This member did not apply.") }) it("disallow leaders to accept members after registration time", async () => { const ts = await getBlockTime(provider) - await contract.connect(wallet).createCompetition(ts + 2, ts + 10) + await contract.connect(wallet).createCompetition(ts + 2, ts + 10, 10) await sleep(2000) - await expect(contract.connect(user0).registerTeam("1", code)).to.be.revertedWith("Registration is closed.") + await expect(contract.connect(user0).createTeam(1, "1", code)).to.be.revertedWith("Registration is closed.") }) it("allow leaders to kick members", async () => { - await contract.connect(user0).registerTeam("1", code) - await contract.connect(user1).createJoinRequest(user0.address) - await contract.connect(user0).approveJoinRequest(user1.address) - let members = await contract.getTeamMembers(user0.address) + await contract.connect(user0).createTeam(0, "1", code) + await contract.connect(user1).createJoinRequest(0, user0.address) + await contract.connect(user0).approveJoinRequest(0, user1.address) + let members = await getTeamMembers(0, user0.address) expect(members).to.include(user1.address) - await contract.connect(user0).removeMember(user1.address) - members = await contract.getTeamMembers(user0.address) + await contract.connect(user0).removeMember(0, user1.address) + members = await getTeamMembers(0, user0.address) expect(members).to.not.include(user1.address) - await contract.connect(user1).createJoinRequest(user0.address) + await contract.connect(user1).createJoinRequest(0, user0.address) }) - it("allow owner to change team size", async () => { - await contract.connect(wallet).setMaxTeamSize(2) + it("allow owner to change competition", async () => { + await contract.connect(wallet).updateCompetition(0, ts + 60, ts + 12, 5); }) - it("disallow non owners to change team size", async () => { - await expect(contract.connect(user0).setMaxTeamSize(2)).to.be.revertedWith("Governable: forbidden") + it("disallow non owners to change competition", async () => { + await expect(contract.connect(user0).updateCompetition(0, ts + 60, ts + 12, 5)).to.be.revertedWith("Governable: forbidden") }) it("disallow leader to accept join request if team is full", async () => { - await contract.connect(user0).registerTeam("1", code) + const ts = await getBlockTime(provider) + + await contract.connect(wallet).updateCompetition(0, ts + 10, ts + 20, 3) + await contract.connect(user0).createTeam(0, "1", code) - await contract.connect(user1).createJoinRequest(user0.address) - await contract.connect(user0).approveJoinRequest(user1.address) + await contract.connect(user1).createJoinRequest(0, user0.address) + await contract.connect(user0).approveJoinRequest(0, user1.address) - await contract.connect(user2).createJoinRequest(user0.address) - await contract.connect(user0).approveJoinRequest(user2.address) + await contract.connect(user2).createJoinRequest(0, user0.address) + await contract.connect(user0).approveJoinRequest(0, user2.address) - await contract.connect(wallet).setMaxTeamSize(2) - await contract.connect(user3).createJoinRequest(user0.address) - await expect(contract.connect(user0).approveJoinRequest(user3.address)).to.be.revertedWith("Team is full.") + await contract.connect(user3).createJoinRequest(0, user0.address) + await expect(contract.connect(user0).approveJoinRequest(0, user3.address)).to.be.revertedWith("Team is full.") }) }); From e3769cf71806bfd7af37b9167c0a487e8abd81e5 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Tue, 30 Aug 2022 19:11:58 +0200 Subject: [PATCH 14/21] wip --- contracts/competition/Competition.sol | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index 24767895..a34900ba 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -139,7 +139,7 @@ contract Competition is Governable emit MemberRemoved(competitionIndex, msg.sender, memberAddress); } - function getCompetition(uint index) external view returns (uint start, uint end, uint maxTeamSize) { + function getCompetition(uint index) external view returns (uint, uint, uint) { return ( competitions[index].start, competitions[index].end, @@ -147,12 +147,12 @@ contract Competition is Governable ); } - function getTeam(uint competitionIndex, address leaderAddr) external view returns (address leader, string memory name, bytes32 referralCode) { + function getTeam(uint competitionIndex, address leaderAddr) external view returns (address, string memory, bytes32) { Team memory team = competitions[competitionIndex].teams[leaderAddr]; return (team.leader, team.name, team.referralCode); } - function getTeamMembers(uint competitionIndex, address leaderAddr, uint start, uint offset) external view returns (address[] memory members) { + function getTeamMembers(uint competitionIndex, address leaderAddr, uint start, uint offset) external view returns (address[] memory) { address[] memory members = competitions[competitionIndex].teams[leaderAddr].members; address[] memory result = new address[](offset); @@ -162,4 +162,8 @@ contract Competition is Governable return result; } + + function getJoinRequest(uint competitionIndex, address memberAddress) external view returns (address) { + return competitions[competitionIndex].joinRequests[memberAddress]; + } } From 9de3be89bb00c3ad84cd18af6bd5f6d43fc4ba0b Mon Sep 17 00:00:00 2001 From: Morazzela Date: Tue, 30 Aug 2022 23:19:57 +0200 Subject: [PATCH 15/21] fixes --- contracts/competition/Competition.sol | 84 ++++++++++++++------------- test/competition/Competition.js | 30 ++++++---- 2 files changed, 64 insertions(+), 50 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index a34900ba..2dee4ce8 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -4,8 +4,7 @@ pragma solidity ^0.6.0; import "../referrals/interfaces/IReferralStorage.sol"; import "../access/Governable.sol"; -contract Competition is Governable -{ +contract Competition is Governable { struct Team { address leader; string name; @@ -23,8 +22,7 @@ contract Competition is Governable mapping(address => address) joinRequests; } - uint public nextCompetitionIndex = 0; - mapping(uint => Competition) public competitions; + Competition[] public competitions; IReferralStorage public referralStorage; event TeamCreated(uint index, address leader, string name, bytes32 referral); @@ -34,19 +32,20 @@ contract Competition is Governable event MemberRemoved(uint index, address leader, address member); event CompetitionCreated(uint index, uint start, uint end, uint maxTeamSize); event CompetitionUpdated(uint index, uint start, uint end, uint maxTeamSize); + event CompetitionRemoved(uint index); modifier registrationIsOpen(uint competitionIndex) { - require(competitions[competitionIndex].start > block.timestamp, "Registration is closed."); + require(competitions[competitionIndex].start > block.timestamp, "Competition: Registration is closed."); _; } modifier isNotMember(uint competitionIndex) { - require(competitions[competitionIndex].memberTeams[msg.sender] == address(0), "Team members are not allowed."); + require(competitions[competitionIndex].memberTeams[msg.sender] == address(0), "Competition: Team members are not allowed."); _; } modifier competitionExists(uint index) { - require(competitions[index].start > 0, "The competition does not exist."); + require(competitions.length > index && competitions[index].start > 0, "Competition: The competition does not exist."); _; } @@ -55,29 +54,34 @@ contract Competition is Governable } function createCompetition(uint start, uint end, uint maxTeamSize) external onlyGov { - require(start > block.timestamp, "Start time must be in the future."); - require(end > start, "End time must be greater than start time."); + _validateCompetitionParameters(start, end, maxTeamSize); - competitions[nextCompetitionIndex] = Competition(start, end, maxTeamSize); + competitions.push(Competition(start, end, maxTeamSize)); - emit CompetitionCreated(nextCompetitionIndex, start, end, maxTeamSize); - - nextCompetitionIndex++; + emit CompetitionCreated(competitions.length - 1, start, end, maxTeamSize); } function updateCompetition(uint index, uint start, uint end, uint maxTeamSize) external onlyGov competitionExists(index) { - competitions[index].start = start; - competitions[index].end = end; - competitions[index].maxTeamSize = maxTeamSize; + _validateCompetitionParameters(start, end, maxTeamSize); + + competitions[index] = Competition(start, end, maxTeamSize); emit CompetitionUpdated(index, start, end, maxTeamSize); } + function removeCompetition(uint index) external onlyGov competitionExists(index) { + require(competitions[index].start > block.timestamp, "Competition: Competition is active.s"); + + delete competitions[index]; + + emit CompetitionRemoved(index); + } + function createTeam(uint competitionIndex, string calldata name, bytes32 referralCode) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) { Competition storage competition = competitions[competitionIndex]; - require(referralStorage.codeOwners(referralCode) != address(0), "Referral code does not exist."); - require(competition.teamNames[name] == false, "Team name already registered."); + require(referralStorage.codeOwners(referralCode) != address(0), "Competition: Referral code does not exist."); + require(!competition.teamNames[name], "Competition: Team name already registered."); Team storage team = competition.teams[msg.sender]; team.leader = msg.sender; @@ -94,8 +98,8 @@ contract Competition is Governable function createJoinRequest(uint competitionIndex, address leaderAddress) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) { Competition storage competition = competitions[competitionIndex]; - require(competition.memberTeams[msg.sender] == address(0), "You can't join multiple teams."); - require(competition.teams[leaderAddress].leader != address(0), "The team does not exist."); + require(competition.memberTeams[msg.sender] == address(0), "Competition: You can't join multiple teams."); + require(competition.teams[leaderAddress].leader != address(0), "Competition: The team does not exist."); competition.joinRequests[msg.sender] = leaderAddress; @@ -105,9 +109,9 @@ contract Competition is Governable function approveJoinRequest(uint competitionIndex, address memberAddress) external registrationIsOpen(competitionIndex) { Competition storage competition = competitions[competitionIndex]; - require(competition.joinRequests[memberAddress] == msg.sender, "This member did not apply."); - require(competition.memberTeams[memberAddress] == address(0), "This member already joined a team."); - require(competition.teams[msg.sender].members.length < competition.maxTeamSize, "Team is full."); + require(competition.joinRequests[memberAddress] == msg.sender, "Competition: This member did not apply."); + require(competition.memberTeams[memberAddress] == address(0), "Competition: This member already joined a team."); + require(competition.teams[msg.sender].members.length < competition.maxTeamSize, "Competition: Team is full."); // referralStorage.setTraderReferralCode(memberAddress, teams[msg.sender].referral); competition.teams[msg.sender].members.push(memberAddress); @@ -125,35 +129,29 @@ contract Competition is Governable function removeMember(uint competitionIndex, address memberAddress) external registrationIsOpen(competitionIndex) { Competition storage competition = competitions[competitionIndex]; - require(competition.memberTeams[memberAddress] == msg.sender, "This member is not in your team"); + require(competition.memberTeams[memberAddress] == msg.sender, "Competition: This member is not in your team"); - for (uint i = 0; i < competition.teams[msg.sender].members.length; i++) { - if (competition.teams[msg.sender].members[i] == memberAddress) { - delete competition.teams[msg.sender].members[i]; - break; + address[] memory oldMembers = competition.teams[msg.sender].members; + address[] memory newMembers = new address[](oldMembers.length - 1); + for (uint i = 0; i < oldMembers.length; i++) { + if (oldMembers[i] != memberAddress) { + newMembers[i] = oldMembers[i]; } } + competition.teams[msg.sender].members = newMembers; competition.memberTeams[memberAddress] = address(0); emit MemberRemoved(competitionIndex, msg.sender, memberAddress); } - function getCompetition(uint index) external view returns (uint, uint, uint) { - return ( - competitions[index].start, - competitions[index].end, - competitions[index].maxTeamSize - ); - } - - function getTeam(uint competitionIndex, address leaderAddr) external view returns (address, string memory, bytes32) { - Team memory team = competitions[competitionIndex].teams[leaderAddr]; + function getTeam(uint competitionIndex, address leaderAddress) external view returns (address, string memory, bytes32) { + Team memory team = competitions[competitionIndex].teams[leaderAddress]; return (team.leader, team.name, team.referralCode); } - function getTeamMembers(uint competitionIndex, address leaderAddr, uint start, uint offset) external view returns (address[] memory) { - address[] memory members = competitions[competitionIndex].teams[leaderAddr].members; + function getTeamMembers(uint competitionIndex, address leaderAddress, uint start, uint offset) external view returns (address[] memory) { + address[] memory members = competitions[competitionIndex].teams[leaderAddress].members; address[] memory result = new address[](offset); for (uint i = start; i < start + offset && i < members.length; i++) { @@ -166,4 +164,10 @@ contract Competition is Governable function getJoinRequest(uint competitionIndex, address memberAddress) external view returns (address) { return competitions[competitionIndex].joinRequests[memberAddress]; } + + function _validateCompetitionParameters(uint start, uint end, uint maxTeamSize) internal { + require(start > block.timestamp, "Competition: Start time must be in the future."); + require(end > start, "Competition: End time must be greater than start time."); + require(maxTeamSize > 0, "Competition: Max team size must be greater than zero."); + } } diff --git a/test/competition/Competition.js b/test/competition/Competition.js index 7070908b..b219c703 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -59,7 +59,7 @@ describe("Competition", function () { const ts = await getBlockTime(provider) await contract.connect(wallet).createCompetition(ts + 2, ts + 60, 10) await sleep(2000); - await expect(contract.connect(user0).createTeam(1, "1", code)).to.be.revertedWith("Registration is closed.") + await expect(contract.connect(user0).createTeam(1, "1", code)).to.be.revertedWith("Competition: Registration is closed.") }) it("allows people to register teams in times", async () => { @@ -68,16 +68,16 @@ describe("Competition", function () { it("disable people to register multiple teams", async () => { await contract.connect(user0).createTeam(0, "1", code) - await expect(contract.connect(user0).createTeam(0, "2", code)).to.be.revertedWith("Team members are not allowed.") + await expect(contract.connect(user0).createTeam(0, "2", code)).to.be.revertedWith("Competition: Team members are not allowed.") }) it("disable people to register a team with non existing referral code", async () => { - await expect(contract.connect(user0).createTeam(0, "1", keccak256("0xFE"))).to.be.revertedWith("Referral code does not exist.") + await expect(contract.connect(user0).createTeam(0, "1", keccak256("0xFE"))).to.be.revertedWith("Competition: Referral code does not exist.") }) it("disable multiple teams with the same name", async () => { await contract.connect(user0).createTeam(0, "1", code) - await expect(contract.connect(user1).createTeam(0, "1", code)).to.be.revertedWith("Team name already registered.") + await expect(contract.connect(user1).createTeam(0, "1", code)).to.be.revertedWith("Competition: Team name already registered.") }) it("allows people to create join requests", async () => { @@ -96,13 +96,13 @@ describe("Competition", function () { await contract.connect(user0).createTeam(0, "1", code) await contract.connect(user1).createJoinRequest(0, user0.address) await contract.connect(user1).cancelJoinRequest(0) - await expect(contract.connect(user0).approveJoinRequest(0, user1.address)).to.be.revertedWith("This member did not apply.") + await expect(contract.connect(user0).approveJoinRequest(0, user1.address)).to.be.revertedWith("Competition: This member did not apply.") }) it("disable team members to create join requests", async () => { await contract.connect(user0).createTeam(0, "1", code) await contract.connect(user1).createTeam(0, "2", code) - await expect(contract.connect(user0).createJoinRequest(0, user1.address)).to.be.revertedWith("Team members are not allowed.") + await expect(contract.connect(user0).createJoinRequest(0, user1.address)).to.be.revertedWith("Competition: Team members are not allowed.") }) it("allows team leaders to accept requests", async () => { @@ -115,14 +115,14 @@ describe("Competition", function () { it("disallow leaders to accept non existant join request", async () => { await contract.connect(user0).createTeam(0, "1", code) - await expect(contract.connect(user0).approveJoinRequest(0, user1.address)).to.be.revertedWith("This member did not apply.") + await expect(contract.connect(user0).approveJoinRequest(0, user1.address)).to.be.revertedWith("Competition: This member did not apply.") }) it("disallow leaders to accept members after registration time", async () => { const ts = await getBlockTime(provider) await contract.connect(wallet).createCompetition(ts + 2, ts + 10, 10) await sleep(2000) - await expect(contract.connect(user0).createTeam(1, "1", code)).to.be.revertedWith("Registration is closed.") + await expect(contract.connect(user0).createTeam(1, "1", code)).to.be.revertedWith("Competition: Registration is closed.") }) it("allow leaders to kick members", async () => { @@ -138,7 +138,7 @@ describe("Competition", function () { }) it("allow owner to change competition", async () => { - await contract.connect(wallet).updateCompetition(0, ts + 60, ts + 12, 5); + await contract.connect(wallet).updateCompetition(0, ts + 60, ts + 120, 5); }) it("disallow non owners to change competition", async () => { @@ -158,6 +158,16 @@ describe("Competition", function () { await contract.connect(user0).approveJoinRequest(0, user2.address) await contract.connect(user3).createJoinRequest(0, user0.address) - await expect(contract.connect(user0).approveJoinRequest(0, user3.address)).to.be.revertedWith("Team is full.") + await expect(contract.connect(user0).approveJoinRequest(0, user3.address)).to.be.revertedWith("Competition: Team is full.") + }) + + it("allow owner to delete competition if it is not started", async () => { + await contract.removeCompetition(0) + const ts = await getBlockTime(provider) + await contract.createCompetition(ts + 2, ts + 10, 10) + await sleep(2000) + await expect(contract.removeCompetition(1)).to.be.revertedWith("Competition: Competition is active.") + await contract.updateCompetition(1, ts + 10, ts + 20, 10) + await expect(contract.connect(user1).removeCompetition(1)).to.be.revertedWith("Governable: forbidden") }) }); From 5d088ceceed2da4bf0b2efac5dbf8b59b4900ada Mon Sep 17 00:00:00 2001 From: Morazzela Date: Wed, 31 Aug 2022 00:58:26 +0200 Subject: [PATCH 16/21] add name validation function & few fixes --- contracts/competition/Competition.sol | 14 +++++++++----- scripts/core/deployCompetition.js | 11 +---------- test/competition/Competition.js | 11 ++++++++++- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index 2dee4ce8..59973557 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -126,12 +126,12 @@ contract Competition is Governable { emit JoinRequestCanceled(competitionIndex, msg.sender); } - function removeMember(uint competitionIndex, address memberAddress) external registrationIsOpen(competitionIndex) { + function removeMember(uint competitionIndex, address leaderAddress, address memberAddress) external registrationIsOpen(competitionIndex) { Competition storage competition = competitions[competitionIndex]; - require(competition.memberTeams[memberAddress] == msg.sender, "Competition: This member is not in your team"); + require(competition.memberTeams[memberAddress] == msg.sender || memberAddress == msg.sender, "Competition: You are not allowed to remove this member."); - address[] memory oldMembers = competition.teams[msg.sender].members; + address[] memory oldMembers = competition.teams[leaderAddress].members; address[] memory newMembers = new address[](oldMembers.length - 1); for (uint i = 0; i < oldMembers.length; i++) { if (oldMembers[i] != memberAddress) { @@ -139,10 +139,10 @@ contract Competition is Governable { } } - competition.teams[msg.sender].members = newMembers; + competition.teams[leaderAddress].members = newMembers; competition.memberTeams[memberAddress] = address(0); - emit MemberRemoved(competitionIndex, msg.sender, memberAddress); + emit MemberRemoved(competitionIndex, leaderAddress, memberAddress); } function getTeam(uint competitionIndex, address leaderAddress) external view returns (address, string memory, bytes32) { @@ -165,6 +165,10 @@ contract Competition is Governable { return competitions[competitionIndex].joinRequests[memberAddress]; } + function validateName(uint competitionIndex, string calldata name) external view returns (bool) { + return !competitions[competitionIndex].teamNames[name]; + } + function _validateCompetitionParameters(uint start, uint end, uint maxTeamSize) internal { require(start > block.timestamp, "Competition: Start time must be in the future."); require(end > start, "Competition: End time must be greater than start time."); diff --git a/scripts/core/deployCompetition.js b/scripts/core/deployCompetition.js index 64b6268b..88437034 100644 --- a/scripts/core/deployCompetition.js +++ b/scripts/core/deployCompetition.js @@ -32,16 +32,7 @@ async function main() { const { positionRouter } = await getValues() const referralStorage = await contractAt("ReferralStorage", await positionRouter.referralStorage()) - const startTime = Math.round(Date.now() / 1000) - const endTime = startTime + 100000000000 - - await deployContract("Competition", [ - startTime, - endTime, - startTime, - endTime, - referralStorage.address, - ]); + await deployContract("Competition", [ referralStorage.address ]); } main() diff --git a/test/competition/Competition.js b/test/competition/Competition.js index b219c703..e144c035 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -131,12 +131,21 @@ describe("Competition", function () { await contract.connect(user0).approveJoinRequest(0, user1.address) let members = await getTeamMembers(0, user0.address) expect(members).to.include(user1.address) - await contract.connect(user0).removeMember(0, user1.address) + await contract.connect(user0).removeMember(0, user0.address, user1.address) members = await getTeamMembers(0, user0.address) expect(members).to.not.include(user1.address) await contract.connect(user1).createJoinRequest(0, user0.address) }) + it("allow members to kick themselves", async () => { + await contract.connect(user0).createTeam(0, "1", code) + await contract.connect(user1).createJoinRequest(0, user0.address) + await contract.connect(user0).approveJoinRequest(0, user1.address) + await contract.connect(user1).removeMember(0, user0.address, user1.address) + members = await getTeamMembers(0, user0.address) + expect(members).to.not.include(user1.address) + }) + it("allow owner to change competition", async () => { await contract.connect(wallet).updateCompetition(0, ts + 60, ts + 120, 5); }) From 1058af4d6638c6364b45846d761a2751610c0406 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Wed, 31 Aug 2022 09:45:02 +0200 Subject: [PATCH 17/21] fix typo --- contracts/competition/Competition.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index 59973557..da68df6d 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -70,7 +70,7 @@ contract Competition is Governable { } function removeCompetition(uint index) external onlyGov competitionExists(index) { - require(competitions[index].start > block.timestamp, "Competition: Competition is active.s"); + require(competitions[index].start > block.timestamp, "Competition: Competition is active."); delete competitions[index]; From 8c88b4b026a0e44e56abda91cd89621be189852a Mon Sep 17 00:00:00 2001 From: Morazzela Date: Sun, 18 Sep 2022 22:24:21 +0200 Subject: [PATCH 18/21] update contract & tests --- contracts/competition/Competition.sol | 61 ++++++++++------- test/competition/Competition.js | 94 +++++++++++++++------------ 2 files changed, 90 insertions(+), 65 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index da68df6d..1b476ea3 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -8,7 +8,6 @@ contract Competition is Governable { struct Team { address leader; string name; - bytes32 referralCode; address[] members; } @@ -20,13 +19,14 @@ contract Competition is Governable { mapping(string => bool) teamNames; mapping(address => address) memberTeams; mapping(address => address) joinRequests; + mapping(address => mapping(address => bytes32)) joinRequestsReferralCodes; } Competition[] public competitions; IReferralStorage public referralStorage; - event TeamCreated(uint index, address leader, string name, bytes32 referral); - event JoinRequestCreated(uint index, address member, address leader); + event TeamCreated(uint index, address leader, string name); + event JoinRequestCreated(uint index, address member, address leader, bytes32 referralCode); event JoinRequestCanceled(uint index, address member); event JoinRequestApproved(uint index, address member, address leader); event MemberRemoved(uint index, address leader, address member); @@ -77,48 +77,57 @@ contract Competition is Governable { emit CompetitionRemoved(index); } - function createTeam(uint competitionIndex, string calldata name, bytes32 referralCode) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) { + function createTeam(uint competitionIndex, string calldata name) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) { Competition storage competition = competitions[competitionIndex]; - require(referralStorage.codeOwners(referralCode) != address(0), "Competition: Referral code does not exist."); require(!competition.teamNames[name], "Competition: Team name already registered."); Team storage team = competition.teams[msg.sender]; team.leader = msg.sender; team.name = name; - team.referralCode = referralCode; team.members.push(msg.sender); competition.teamNames[name] = true; competition.memberTeams[msg.sender] = msg.sender; - emit TeamCreated(competitionIndex, msg.sender, name, referralCode); + emit TeamCreated(competitionIndex, msg.sender, name); } - function createJoinRequest(uint competitionIndex, address leaderAddress) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) { + function createJoinRequest(uint competitionIndex, address leaderAddress, bytes32 referralCode) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) { Competition storage competition = competitions[competitionIndex]; require(competition.memberTeams[msg.sender] == address(0), "Competition: You can't join multiple teams."); require(competition.teams[leaderAddress].leader != address(0), "Competition: The team does not exist."); + if (referralCode != bytes32(0)) { + require(referralStorage.codeOwners(referralCode) != address(0), "Competition: The referral code does not exist."); + } + competition.joinRequests[msg.sender] = leaderAddress; + competition.joinRequestsReferralCodes[msg.sender][leaderAddress] = referralCode; - emit JoinRequestCreated(competitionIndex, msg.sender, leaderAddress); + emit JoinRequestCreated(competitionIndex, msg.sender, leaderAddress, referralCode); } - function approveJoinRequest(uint competitionIndex, address memberAddress) external registrationIsOpen(competitionIndex) { + function approveJoinRequest(uint competitionIndex, address[] calldata memberAddresses) external registrationIsOpen(competitionIndex) { Competition storage competition = competitions[competitionIndex]; - require(competition.joinRequests[memberAddress] == msg.sender, "Competition: This member did not apply."); - require(competition.memberTeams[memberAddress] == address(0), "Competition: This member already joined a team."); - require(competition.teams[msg.sender].members.length < competition.maxTeamSize, "Competition: Team is full."); + for (uint i = 0; i < memberAddresses.length; i++) { + address memberAddress = memberAddresses[i]; + require(competition.joinRequests[memberAddress] == msg.sender, "Competition: Member did not apply."); + require(competition.memberTeams[memberAddress] == address(0), "Competition: Member already joined a team."); + require(competition.teams[msg.sender].members.length < competition.maxTeamSize, "Competition: Team is full."); + + if (competition.joinRequestsReferralCodes[memberAddress][msg.sender] != bytes32(0)) { + referralStorage.setTraderReferralCode(memberAddress, competition.joinRequestsReferralCodes[memberAddress][msg.sender]); + } - // referralStorage.setTraderReferralCode(memberAddress, teams[msg.sender].referral); - competition.teams[msg.sender].members.push(memberAddress); - competition.memberTeams[memberAddress] = msg.sender; - competition.joinRequests[memberAddress] = address(0); + competition.teams[msg.sender].members.push(memberAddress); + competition.memberTeams[memberAddress] = msg.sender; + competition.joinRequests[memberAddress] = address(0); - emit JoinRequestApproved(competitionIndex, memberAddress, msg.sender); + emit JoinRequestApproved(competitionIndex, memberAddress, msg.sender); + } } function cancelJoinRequest(uint competitionIndex) external registrationIsOpen(competitionIndex) { @@ -145,12 +154,12 @@ contract Competition is Governable { emit MemberRemoved(competitionIndex, leaderAddress, memberAddress); } - function getTeam(uint competitionIndex, address leaderAddress) external view returns (address, string memory, bytes32) { - Team memory team = competitions[competitionIndex].teams[leaderAddress]; - return (team.leader, team.name, team.referralCode); + function getTeam(uint competitionIndex, address _leaderAddress) external view returns (address leaderAddress, string memory name) { + Team memory team = competitions[competitionIndex].teams[_leaderAddress]; + return (team.leader, team.name); } - function getTeamMembers(uint competitionIndex, address leaderAddress, uint start, uint offset) external view returns (address[] memory) { + function getTeamMembers(uint competitionIndex, address leaderAddress, uint start, uint offset) external view returns (address[] memory members) { address[] memory members = competitions[competitionIndex].teams[leaderAddress].members; address[] memory result = new address[](offset); @@ -161,11 +170,15 @@ contract Competition is Governable { return result; } - function getJoinRequest(uint competitionIndex, address memberAddress) external view returns (address) { + function getMemberTeam(uint competitionIndex, address memberAddress) external view returns (address leaderAddress) { + return competitions[competitionIndex].memberTeams[memberAddress]; + } + + function getJoinRequest(uint competitionIndex, address memberAddress) external view returns (address leaderAddress) { return competitions[competitionIndex].joinRequests[memberAddress]; } - function validateName(uint competitionIndex, string calldata name) external view returns (bool) { + function validateName(uint competitionIndex, string calldata name) external view returns (bool isValid) { return !competitions[competitionIndex].teamNames[name]; } diff --git a/test/competition/Competition.js b/test/competition/Competition.js index e144c035..50d6606f 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -16,6 +16,7 @@ describe("Competition", function () { let ts let referralStorage let code = keccak256("0xFF") + let nullCode = ethers.constants.HashZero async function getTeamMembers(index, addr) { @@ -43,6 +44,7 @@ describe("Competition", function () { ts = await getBlockTime(provider) referralStorage = await deployContract("ReferralStorage", []) contract = await deployContract("Competition", [referralStorage.address]); + await referralStorage.setHandler(contract.address, true) await referralStorage.registerCode(code) await contract.createCompetition(ts + 10, ts + 20, 10) }) @@ -59,88 +61,90 @@ describe("Competition", function () { const ts = await getBlockTime(provider) await contract.connect(wallet).createCompetition(ts + 2, ts + 60, 10) await sleep(2000); - await expect(contract.connect(user0).createTeam(1, "1", code)).to.be.revertedWith("Competition: Registration is closed.") + await expect(contract.connect(user0).createTeam(1, "1")).to.be.revertedWith("Competition: Registration is closed.") }) it("allows people to register teams in times", async () => { - await contract.connect(user0).createTeam(0, "1", code) + await contract.connect(user0).createTeam(0, "1") }) it("disable people to register multiple teams", async () => { - await contract.connect(user0).createTeam(0, "1", code) - await expect(contract.connect(user0).createTeam(0, "2", code)).to.be.revertedWith("Competition: Team members are not allowed.") + await contract.connect(user0).createTeam(0, "1") + await expect(contract.connect(user0).createTeam(0, "2")).to.be.revertedWith("Competition: Team members are not allowed.") }) - it("disable people to register a team with non existing referral code", async () => { - await expect(contract.connect(user0).createTeam(0, "1", keccak256("0xFE"))).to.be.revertedWith("Competition: Referral code does not exist.") - }) + // it("disable people to register a team with non existing referral code", async () => { + // await expect(contract.connect(user0).createTeam(0, "1", keccak256("0xFE"))).to.be.revertedWith("Competition: Referral code does not exist.") + // }) it("disable multiple teams with the same name", async () => { - await contract.connect(user0).createTeam(0, "1", code) - await expect(contract.connect(user1).createTeam(0, "1", code)).to.be.revertedWith("Competition: Team name already registered.") + await contract.connect(user0).createTeam(0, "1") + await expect(contract.connect(user1).createTeam(0, "1")).to.be.revertedWith("Competition: Team name already registered.") }) it("allows people to create join requests", async () => { - await contract.connect(user0).createTeam(0, "1", code) - await contract.connect(user1).createJoinRequest(0, user0.address) + await contract.connect(user0).createTeam(0, "1") + await contract.connect(user1).createJoinRequest(0, user0.address, nullCode); + await contract.connect(user2).createJoinRequest(0, user0.address, code); }) it("allows people to replace join requests", async () => { - await contract.connect(user0).createTeam(0, "1", code) - await contract.connect(user1).createTeam(0, "2", code) - await contract.connect(user2).createJoinRequest(0, user0.address) - await contract.connect(user2).createJoinRequest(0, user1.address) + await contract.connect(user0).createTeam(0, "1") + await contract.connect(user1).createTeam(0, "2") + await contract.connect(user2).createJoinRequest(0, user0.address, nullCode) + await contract.connect(user2).createJoinRequest(0, user1.address, nullCode) }) it("allow people to cancel join requests", async () => { - await contract.connect(user0).createTeam(0, "1", code) - await contract.connect(user1).createJoinRequest(0, user0.address) + await contract.connect(user0).createTeam(0, "1") + await contract.connect(user1).createJoinRequest(0, user0.address, nullCode) await contract.connect(user1).cancelJoinRequest(0) - await expect(contract.connect(user0).approveJoinRequest(0, user1.address)).to.be.revertedWith("Competition: This member did not apply.") + await expect(contract.connect(user0).approveJoinRequest(0, [user1.address])).to.be.revertedWith("Competition: Member did not apply.") }) it("disable team members to create join requests", async () => { - await contract.connect(user0).createTeam(0, "1", code) - await contract.connect(user1).createTeam(0, "2", code) - await expect(contract.connect(user0).createJoinRequest(0, user1.address)).to.be.revertedWith("Competition: Team members are not allowed.") + await contract.connect(user0).createTeam(0, "1") + await contract.connect(user1).createTeam(0, "2") + await expect(contract.connect(user0).createJoinRequest(0, user1.address, nullCode)).to.be.revertedWith("Competition: Team members are not allowed.") }) it("allows team leaders to accept requests", async () => { - await contract.connect(user0).createTeam(0, "1", code) - await contract.connect(user1).createJoinRequest(0, user0.address) - await contract.connect(user0).approveJoinRequest(0, user1.address) + await contract.connect(user0).createTeam(0, "1") + await contract.connect(user1).createJoinRequest(0, user0.address, code) + await contract.connect(user2).createJoinRequest(0, user0.address, code) + await contract.connect(user0).approveJoinRequest(0, [user1.address, user2.address]) const members = await getTeamMembers(0, user0.address) expect(members).to.include(user1.address) + expect(members).to.include(user2.address) }) it("disallow leaders to accept non existant join request", async () => { - await contract.connect(user0).createTeam(0, "1", code) - await expect(contract.connect(user0).approveJoinRequest(0, user1.address)).to.be.revertedWith("Competition: This member did not apply.") + await contract.connect(user0).createTeam(0, "1") + await expect(contract.connect(user0).approveJoinRequest(0, [user1.address])).to.be.revertedWith("Competition: Member did not apply.") }) it("disallow leaders to accept members after registration time", async () => { const ts = await getBlockTime(provider) await contract.connect(wallet).createCompetition(ts + 2, ts + 10, 10) await sleep(2000) - await expect(contract.connect(user0).createTeam(1, "1", code)).to.be.revertedWith("Competition: Registration is closed.") + await expect(contract.connect(user0).createTeam(1, "1")).to.be.revertedWith("Competition: Registration is closed.") }) it("allow leaders to kick members", async () => { - await contract.connect(user0).createTeam(0, "1", code) - await contract.connect(user1).createJoinRequest(0, user0.address) - await contract.connect(user0).approveJoinRequest(0, user1.address) + await contract.connect(user0).createTeam(0, "1") + await contract.connect(user1).createJoinRequest(0, user0.address, nullCode) + await contract.connect(user0).approveJoinRequest(0, [user1.address]) let members = await getTeamMembers(0, user0.address) expect(members).to.include(user1.address) await contract.connect(user0).removeMember(0, user0.address, user1.address) members = await getTeamMembers(0, user0.address) expect(members).to.not.include(user1.address) - await contract.connect(user1).createJoinRequest(0, user0.address) }) it("allow members to kick themselves", async () => { - await contract.connect(user0).createTeam(0, "1", code) - await contract.connect(user1).createJoinRequest(0, user0.address) - await contract.connect(user0).approveJoinRequest(0, user1.address) + await contract.connect(user0).createTeam(0, "1") + await contract.connect(user1).createJoinRequest(0, user0.address, nullCode) + await contract.connect(user0).approveJoinRequest(0, [user1.address]) await contract.connect(user1).removeMember(0, user0.address, user1.address) members = await getTeamMembers(0, user0.address) expect(members).to.not.include(user1.address) @@ -158,16 +162,16 @@ describe("Competition", function () { const ts = await getBlockTime(provider) await contract.connect(wallet).updateCompetition(0, ts + 10, ts + 20, 3) - await contract.connect(user0).createTeam(0, "1", code) + await contract.connect(user0).createTeam(0, "1") - await contract.connect(user1).createJoinRequest(0, user0.address) - await contract.connect(user0).approveJoinRequest(0, user1.address) + await contract.connect(user1).createJoinRequest(0, user0.address, nullCode) + await contract.connect(user0).approveJoinRequest(0, [user1.address]) - await contract.connect(user2).createJoinRequest(0, user0.address) - await contract.connect(user0).approveJoinRequest(0, user2.address) + await contract.connect(user2).createJoinRequest(0, user0.address, nullCode) + await contract.connect(user0).approveJoinRequest(0, [user2.address]) - await contract.connect(user3).createJoinRequest(0, user0.address) - await expect(contract.connect(user0).approveJoinRequest(0, user3.address)).to.be.revertedWith("Competition: Team is full.") + await contract.connect(user3).createJoinRequest(0, user0.address, nullCode) + await expect(contract.connect(user0).approveJoinRequest(0, [user3.address])).to.be.revertedWith("Competition: Team is full.") }) it("allow owner to delete competition if it is not started", async () => { @@ -179,4 +183,12 @@ describe("Competition", function () { await contract.updateCompetition(1, ts + 10, ts + 20, 10) await expect(contract.connect(user1).removeCompetition(1)).to.be.revertedWith("Governable: forbidden") }) + + it("sets the referral code to the members when the request is approved", async () => { + await contract.createTeam(0, "a") + await contract.connect(user0).createJoinRequest(0, wallet.address, code) + await contract.approveJoinRequest(0, [user0.address]) + const traderCode = await referralStorage.getTraderReferralInfo(user0.address) + expect(traderCode[0]).to.be.equal(code) + }) }); From dbd1b8a45384da5717dc85feae90c9f30d7ef89e Mon Sep 17 00:00:00 2001 From: Morazzela Date: Mon, 19 Sep 2022 23:25:01 +0200 Subject: [PATCH 19/21] add small test + test scripts --- scripts/competition/createCompetition.js | 19 +++++++++++++++++++ scripts/competition/updateCompetition.js | 19 +++++++++++++++++++ scripts/core/deployCompetition.js | 14 ++++++++------ test/competition/Competition.js | 2 ++ 4 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 scripts/competition/createCompetition.js create mode 100644 scripts/competition/updateCompetition.js diff --git a/scripts/competition/createCompetition.js b/scripts/competition/createCompetition.js new file mode 100644 index 00000000..3c5c11f5 --- /dev/null +++ b/scripts/competition/createCompetition.js @@ -0,0 +1,19 @@ +const { ethers } = require("hardhat"); +const { contractAt, sendTxn } = require("../shared/helpers") + +async function main() { + const competition = await contractAt("Competition", "0x271B8D7b97A07207BAd07dc577F6D29D6a368C56"); + + await sendTxn(competition.createCompetition( + 1663884000, + 1664488800, + 5 + ), "competition.createCompetition(start, end, maxTeamSize)") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/competition/updateCompetition.js b/scripts/competition/updateCompetition.js new file mode 100644 index 00000000..f78b60a5 --- /dev/null +++ b/scripts/competition/updateCompetition.js @@ -0,0 +1,19 @@ +const { contractAt, sendTxn } = require("../shared/helpers") + +async function main() { + const competition = await contractAt("Competition", "0x17fb5AEEF7221353B6B2D12EDDa0Dd5655Ec25b2"); + + await sendTxn(competition.updateCompetition( + 0, + 1672527599, + 1704063599, + 10 + ), "competition.updateCompetition(index, start, end, maxTeamSize)") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/deployCompetition.js b/scripts/core/deployCompetition.js index 88437034..7b00f5ea 100644 --- a/scripts/core/deployCompetition.js +++ b/scripts/core/deployCompetition.js @@ -14,6 +14,12 @@ async function getAvaxValues() { return { positionRouter } } +// async function getArbitrumTestnetValues() { +// const positionRouter = await contractAt("PositionRouter", "0x195256074192170d1530527abC9943759c7167d8") + +// return { positionRouter } +// } + async function getValues() { if (network === "arbitrum") { return getArbValues() @@ -22,15 +28,11 @@ async function getValues() { if (network === "avax") { return getAvaxValues() } - - if (network === "testnet") { - return getTestnetValues() - } } async function main() { - const { positionRouter } = await getValues() - const referralStorage = await contractAt("ReferralStorage", await positionRouter.referralStorage()) + // const { positionRouter } = await getValues() + const referralStorage = await contractAt("ReferralStorage", "0x902B74dAe2fff3BA564BDa930A7D687b84e0E9cC") await deployContract("Competition", [ referralStorage.address ]); } diff --git a/test/competition/Competition.js b/test/competition/Competition.js index 50d6606f..cc57add9 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -148,6 +148,8 @@ describe("Competition", function () { await contract.connect(user1).removeMember(0, user0.address, user1.address) members = await getTeamMembers(0, user0.address) expect(members).to.not.include(user1.address) + const team = await contract.getMemberTeam(0, user1.address) + expect(team).to.be.equal(ethers.constants.AddressZero) }) it("allow owner to change competition", async () => { From ad48d55afc549e6dd47d2abc7903e0e8b355cc35 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Mon, 26 Sep 2022 00:18:27 +0200 Subject: [PATCH 20/21] fix removeMember function --- contracts/competition/Competition.sol | 9 +++++---- test/competition/Competition.js | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index 1b476ea3..a9b6c66e 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -64,7 +64,9 @@ contract Competition is Governable { function updateCompetition(uint index, uint start, uint end, uint maxTeamSize) external onlyGov competitionExists(index) { _validateCompetitionParameters(start, end, maxTeamSize); - competitions[index] = Competition(start, end, maxTeamSize); + competitions[index].start = start; + competitions[index].end = end; + competitions[index].maxTeamSize = maxTeamSize; emit CompetitionUpdated(index, start, end, maxTeamSize); } @@ -141,14 +143,13 @@ contract Competition is Governable { require(competition.memberTeams[memberAddress] == msg.sender || memberAddress == msg.sender, "Competition: You are not allowed to remove this member."); address[] memory oldMembers = competition.teams[leaderAddress].members; - address[] memory newMembers = new address[](oldMembers.length - 1); + delete competition.teams[leaderAddress].members; for (uint i = 0; i < oldMembers.length; i++) { if (oldMembers[i] != memberAddress) { - newMembers[i] = oldMembers[i]; + competition.teams[leaderAddress].members.push(oldMembers[i]); } } - competition.teams[leaderAddress].members = newMembers; competition.memberTeams[memberAddress] = address(0); emit MemberRemoved(competitionIndex, leaderAddress, memberAddress); diff --git a/test/competition/Competition.js b/test/competition/Competition.js index cc57add9..341b2561 100644 --- a/test/competition/Competition.js +++ b/test/competition/Competition.js @@ -131,14 +131,21 @@ describe("Competition", function () { }) it("allow leaders to kick members", async () => { + await contract.updateCompetition(0, ts + 1000, ts + 2000, 10); await contract.connect(user0).createTeam(0, "1") await contract.connect(user1).createJoinRequest(0, user0.address, nullCode) await contract.connect(user0).approveJoinRequest(0, [user1.address]) + await contract.connect(user2).createJoinRequest(0, user0.address, nullCode) + await contract.connect(user0).approveJoinRequest(0, [user2.address]) let members = await getTeamMembers(0, user0.address) expect(members).to.include(user1.address) + expect(members).to.include(user2.address) await contract.connect(user0).removeMember(0, user0.address, user1.address) members = await getTeamMembers(0, user0.address) + expect(members).to.include(user2.address) expect(members).to.not.include(user1.address) + const team = await contract.getMemberTeam(0, user1.address) + expect(team).to.be.equal(ethers.constants.AddressZero) }) it("allow members to kick themselves", async () => { @@ -153,7 +160,13 @@ describe("Competition", function () { }) it("allow owner to change competition", async () => { - await contract.connect(wallet).updateCompetition(0, ts + 60, ts + 120, 5); + const newStart = ts + 60 + const newEnd = ts + 120 + await contract.connect(wallet).updateCompetition(0, newStart, newEnd, 5); + const res = await contract.competitions(0) + expect(res.start.toNumber()).to.be.equal(newStart) + expect(res.end.toNumber()).to.be.equal(newEnd) + expect(res.maxTeamSize).to.be.equal(5) }) it("disallow non owners to change competition", async () => { From 37e711e7566e222308739e176fdfea3c8e8ad1c8 Mon Sep 17 00:00:00 2001 From: Morazzela Date: Sun, 27 Nov 2022 21:52:10 +0100 Subject: [PATCH 21/21] add competition type (individual/teams/both) --- contracts/competition/Competition.sol | 29 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/contracts/competition/Competition.sol b/contracts/competition/Competition.sol index a9b6c66e..2d016225 100644 --- a/contracts/competition/Competition.sol +++ b/contracts/competition/Competition.sol @@ -15,6 +15,7 @@ contract Competition is Governable { uint start; uint end; uint maxTeamSize; + uint8 competitionType; mapping(address => Team) teams; mapping(string => bool) teamNames; mapping(address => address) memberTeams; @@ -30,8 +31,8 @@ contract Competition is Governable { event JoinRequestCanceled(uint index, address member); event JoinRequestApproved(uint index, address member, address leader); event MemberRemoved(uint index, address leader, address member); - event CompetitionCreated(uint index, uint start, uint end, uint maxTeamSize); - event CompetitionUpdated(uint index, uint start, uint end, uint maxTeamSize); + event CompetitionCreated(uint index, uint start, uint end, uint maxTeamSize, uint8 competitionType); + event CompetitionUpdated(uint index, uint start, uint end, uint maxTeamSize, uint8 competitionType); event CompetitionRemoved(uint index); modifier registrationIsOpen(uint competitionIndex) { @@ -49,26 +50,32 @@ contract Competition is Governable { _; } + modifier teamsAreAllowed(uint index) { + require(competitions[competitionIndex].competitionType !== 0, "Competition: Team are not allowed."); + _; + } + constructor(IReferralStorage _referralStorage) public { referralStorage = _referralStorage; } - function createCompetition(uint start, uint end, uint maxTeamSize) external onlyGov { + function createCompetition(uint start, uint end, uint maxTeamSize, uint8 competitionType) external onlyGov { _validateCompetitionParameters(start, end, maxTeamSize); - competitions.push(Competition(start, end, maxTeamSize)); + competitions.push(Competition(start, end, maxTeamSize, competitionType)); emit CompetitionCreated(competitions.length - 1, start, end, maxTeamSize); } - function updateCompetition(uint index, uint start, uint end, uint maxTeamSize) external onlyGov competitionExists(index) { + function updateCompetition(uint index, uint start, uint end, uint maxTeamSize, uint8 competitionType) external onlyGov competitionExists(index) { _validateCompetitionParameters(start, end, maxTeamSize); competitions[index].start = start; competitions[index].end = end; competitions[index].maxTeamSize = maxTeamSize; + competitions[index].competitionType = competitionType; - emit CompetitionUpdated(index, start, end, maxTeamSize); + emit CompetitionUpdated(index, start, end, maxTeamSize, competitionType); } function removeCompetition(uint index) external onlyGov competitionExists(index) { @@ -79,7 +86,7 @@ contract Competition is Governable { emit CompetitionRemoved(index); } - function createTeam(uint competitionIndex, string calldata name) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) { + function createTeam(uint competitionIndex, string calldata name) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) teamsAreAllowed(competitionIndex) { Competition storage competition = competitions[competitionIndex]; require(!competition.teamNames[name], "Competition: Team name already registered."); @@ -95,7 +102,7 @@ contract Competition is Governable { emit TeamCreated(competitionIndex, msg.sender, name); } - function createJoinRequest(uint competitionIndex, address leaderAddress, bytes32 referralCode) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) { + function createJoinRequest(uint competitionIndex, address leaderAddress, bytes32 referralCode) external registrationIsOpen(competitionIndex) isNotMember(competitionIndex) teamsAreAllowed(competitionIndex) { Competition storage competition = competitions[competitionIndex]; require(competition.memberTeams[msg.sender] == address(0), "Competition: You can't join multiple teams."); @@ -111,7 +118,7 @@ contract Competition is Governable { emit JoinRequestCreated(competitionIndex, msg.sender, leaderAddress, referralCode); } - function approveJoinRequest(uint competitionIndex, address[] calldata memberAddresses) external registrationIsOpen(competitionIndex) { + function approveJoinRequest(uint competitionIndex, address[] calldata memberAddresses) external registrationIsOpen(competitionIndex) teamsAreAllowed(competitionIndex) { Competition storage competition = competitions[competitionIndex]; for (uint i = 0; i < memberAddresses.length; i++) { @@ -132,12 +139,12 @@ contract Competition is Governable { } } - function cancelJoinRequest(uint competitionIndex) external registrationIsOpen(competitionIndex) { + function cancelJoinRequest(uint competitionIndex) external registrationIsOpen(competitionIndex) teamsAreAllowed(competitionIndex) { competitions[competitionIndex].joinRequests[msg.sender] = address(0); emit JoinRequestCanceled(competitionIndex, msg.sender); } - function removeMember(uint competitionIndex, address leaderAddress, address memberAddress) external registrationIsOpen(competitionIndex) { + function removeMember(uint competitionIndex, address leaderAddress, address memberAddress) external registrationIsOpen(competitionIndex) teamsAreAllowed(competitionIndex) { Competition storage competition = competitions[competitionIndex]; require(competition.memberTeams[memberAddress] == msg.sender || memberAddress == msg.sender, "Competition: You are not allowed to remove this member.");