Skip to content
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ data/snapshotBalance
data/holders
flattened
distribution-data*.json
.DS_Store

#Hardhat files
cache
Expand Down
169 changes: 169 additions & 0 deletions contracts/competition/Competition.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "../referrals/interfaces/IReferralStorage.sol";
import "../access/Governable.sol";

contract Competition is Governable
Comment thread
morazzela marked this conversation as resolved.
Outdated
{
Comment thread
morazzela marked this conversation as resolved.
Outdated
struct Team {
address leader;
string name;
bytes32 referralCode;
address[] members;
}

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;
mapping(uint => Competition) public competitions;
Comment thread
morazzela marked this conversation as resolved.
Outdated
IReferralStorage public referralStorage;

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);
Comment thread
morazzela marked this conversation as resolved.
Outdated

modifier registrationIsOpen(uint competitionIndex) {
require(competitions[competitionIndex].start > block.timestamp, "Registration is closed.");
Comment thread
morazzela marked this conversation as resolved.
Outdated
_;
}

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.");
_;
}

constructor(IReferralStorage _referralStorage) public {
referralStorage = _referralStorage;
}

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.");
Comment thread
morazzela marked this conversation as resolved.
Outdated

competitions[nextCompetitionIndex] = Competition(start, end, maxTeamSize);

emit CompetitionCreated(nextCompetitionIndex, start, end, maxTeamSize);

nextCompetitionIndex++;
}

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;
Comment thread
morazzela marked this conversation as resolved.

emit CompetitionUpdated(index, start, end, maxTeamSize);
}

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.");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xdev10 you've said we can skip the referral code, do you mean referral code will be optional?

require(competition.teamNames[name] == false, "Team name already registered.");
Comment thread
morazzela marked this conversation as resolved.
Outdated

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);
}

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.");

competition.joinRequests[msg.sender] = leaderAddress;

emit JoinRequestCreated(competitionIndex, msg.sender, leaderAddress);
}

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);
competition.teams[msg.sender].members.push(memberAddress);
competition.memberTeams[memberAddress] = msg.sender;
competition.joinRequests[memberAddress] = address(0);

emit JoinRequestApproved(competitionIndex, memberAddress, msg.sender);
}

function cancelJoinRequest(uint competitionIndex) external registrationIsOpen(competitionIndex) {
competitions[competitionIndex].joinRequests[msg.sender] = address(0);
emit JoinRequestCanceled(competitionIndex, msg.sender);
}

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");

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];
Comment thread
morazzela marked this conversation as resolved.
Outdated
break;
}
}

competition.memberTeams[memberAddress] = address(0);

emit MemberRemoved(competitionIndex, msg.sender, memberAddress);
}

function getCompetition(uint index) external view returns (uint, uint, uint) {
Comment thread
morazzela marked this conversation as resolved.
Outdated
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];
return (team.leader, team.name, team.referralCode);
}

function getTeamMembers(uint competitionIndex, address leaderAddr, uint start, uint offset) external view returns (address[] memory) {
Comment thread
morazzela marked this conversation as resolved.
Outdated
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;
}

function getJoinRequest(uint competitionIndex, address memberAddress) external view returns (address) {
return competitions[competitionIndex].joinRequests[memberAddress];
}
}
52 changes: 52 additions & 0 deletions scripts/core/deployCompetition.js
Original file line number Diff line number Diff line change
@@ -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)
})
163 changes: 163 additions & 0 deletions test/competition/Competition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
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")

use(solidity)

const { keccak256 } = ethers.utils

describe("Competition", function () {
const provider = waffle.provider
const [wallet, user0, user1, user2, user3] = provider.getWallets()
let contract
let ts
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, 10)
})

it("disable non owners to set competition details", async () => {
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, 10)
await sleep(2000);
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).createTeam(0, "1", code)
})

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.")
})

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.")
})

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.")
})

it("allows people to create join requests", async () => {
await contract.connect(user0).createTeam(0, "1", code)
await contract.connect(user1).createJoinRequest(0, user0.address)
})

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).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).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).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).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, 10)
await sleep(2000)
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).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(0, 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 owner to change competition", async () => {
await contract.connect(wallet).updateCompetition(0, ts + 60, ts + 12, 5);
})

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 () => {
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(0, user0.address)
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(user3).createJoinRequest(0, user0.address)
await expect(contract.connect(user0).approveJoinRequest(0, user3.address)).to.be.revertedWith("Team is full.")
})
});
Loading