Skip to content

Commit

Permalink
64 principal game cost time parameters (#82)
Browse files Browse the repository at this point in the history
* wip

* removed multipass sources

* contracts compile, tests are broken, new assertions are not tested yet

* rm white spaces

* happy linter - happy life

* TDD: Todo Rewards, ToDo More tests

* Fixed some tests - due to removed facet indies moved down

* TDD, fixes

* happy linter - happy life

* removed additional rank requirement functionality

* burning payments instead of refunding

* give dao benefit 10% of game revenue being burnt

* added beneficiary share from games as 10%, and helper methods

* added principal cost basic test

* can  generate zero votes in tests

* refactored the LibTurnBasedGame.sol startGameEarly and StartGame functions to have more reusable code

* added minimal time to the constraints

* fix tests

* ignore vscode

* happy linter - happy life

* changeset updated

* more Game Cost and Time Tests

* remove .only

* docstrings

* update readme
  • Loading branch information
peersky authored Nov 26, 2024
1 parent 3cfd71f commit c53987d
Show file tree
Hide file tree
Showing 54 changed files with 1,543 additions and 3,573 deletions.
78 changes: 78 additions & 0 deletions .changeset/clever-monkeys-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
'rankify-contracts': minor
---

# Changeset for branch 64-principal-game-cost-time-parameters

## Summary
This branch introduces significant changes to game cost and time parameters, payment handling, and rank token mechanics, along with several code improvements and bug fixes.

## Changes

### Core Game Mechanics
- `LibRankify.sol`:
- Introduced principal game cost calculation based on game time
- Added minimum game time validation and constraints
- Implemented 90/10 payment split: 90% burned, 10% to DAO
- Removed payment refunds and game cancellation payments
- Simplified rank token rewards to only top player
- Added validation for turn count and game time parameters

### Libraries
- `LibTBG.sol`:
- Added `startedAt` timestamp for minimum game time tracking
- Renamed `getGameSettings()` to `getSettings(uint256 gameId)` for better clarity
- Updated storage access patterns for overtime functionality
- Simplified tie detection logic to only consider top 2 players
- Fixed storage slot access patterns

### Tokens
- `DistributableGovernanceERC20.sol`:
- Updated Solidity version from 0.8.20 to 0.8.28

- `RankToken.sol`:
- Updated Solidity version from ^0.8.20 to =0.8.28
- Added IERC165 import
- Implemented burn function with ERC7746C middleware

### Vendor
- Renamed `DiamondCloneable.sol` to `DiamondClonable.sol`:
- Fixed typo in error name from 'fucntionDoesNotExist' to 'functionDoesNotExist'

- `DiamondLoupeFacet.sol`:
- Updated Solidity version to ^0.8.28

- `LibDiamond.sol`:
- Added DuplicateSignature error definition

### Removed Files
- Deleted `test/DNSFacet.ts`
- Removed multipass sources:
- `src/facets/DNSFacet.sol`
- `src/initializers/MultipassInit.sol`
- `src/libraries/LibMultipass.sol`
- `src/interfaces/IMultipass.sol`

### Mocks
- `RankifyInstanceEventMock.sol`:
- Fixed typo in parameter name from 'proposerIndicies' to 'proposerIndices'

## Breaking Changes
- Storage layout changes in LibTBG require careful migration
- Payment handling completely reworked:
- Removed refunds functionality
- Implemented burn mechanism for 90% of payments
- Added DAO benefit for 10% of payments
- Rank token rewards simplified to only top player
- Solidity version updates may require dependency updates
- Renamed Diamond contract file requires build script updates
- Removed all multipass functionality

## Migration Guide
1. Update build scripts to reference new DiamondClonable filename
2. Verify storage layout compatibility after LibTBG changes
3. Update dependencies to support Solidity 0.8.28
4. Remove any references to multipass functionality
5. Update payment handling code to work with new burn mechanism
6. Adjust rank token distribution logic for single winner
7. Ensure game time parameters meet new constraints
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/node_modules
/.pnp
.pnp.js
.vscode

# testing
/coverage
Expand Down
44 changes: 24 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,24 +66,19 @@ const distributorArguments: MAODistribution.DistributorArgumentsStruct = {
tokenName: 'tokenName',
tokenSymbol: 'tokenSymbol',
},
ACIDSettings: {
RankifySettings: {
RankTokenContractURI: 'https://example.com/rank',
gamePrice: 1,
joinGamePrice: 1,
maxPlayersSize: 16,
maxTurns: 1,
principalCost: RInstanceSettings.PRINCIPAL_COST,
principalTimeConstant: RInstanceSettings.PRINCIPAL_TIME_CONSTANT,
metadata: ethers.utils.hexlify(ethers.utils.toUtf8Bytes('metadata')),
minPlayersSize: 4,
paymentToken: rankify.address,
rankTokenURI: 'https://example.com/rank',
timePerTurn: 1,
timeToJoin: 1,
voteCredits: 14,
},
};
// const abi = import('../abi/src/distributions/MAODistribution.sol/MAODistribution.json');
// Encode the arguments
const data = ethers.utils.defaultAbiCoder.encode(
[
'tuple(tuple(string daoURI, string subdomain, bytes metadata, string tokenName, string tokenSymbol) DAOSEttings, tuple(uint256 timePerTurn, uint256 maxPlayersSize, uint256 minPlayersSize, uint256 timeToJoin, uint256 maxTurns, uint256 voteCredits, uint256 gamePrice, address paymentToken, uint256 joinGamePrice, string metadata, string rankTokenURI, string RankTokenContractURI) ACIDSettings)',
'tuple(tuple(string daoURI, string subdomain, bytes metadata, string tokenName, string tokenSymbol) DAOSEttings, tuple(uint256 principalCost, uint256 principalTimeConstant, string metadata, string rankTokenURI, string RankTokenContractURI) RankifySettings)',
],
[distributorArguments],
);
Expand All @@ -93,19 +88,28 @@ const tx = await distributorContract.instantiate(distributorsDistId, data);

In order to get `distributorsDistId` you can call `getDistributions` at `PeeramidLabsDistributor` contract and look for. We will host a public API to get the list of distributions soon.

### ACID distribution
### ACID Distribution (Autonomous Competence Identification Distribution)

[ArguableVotingTournament.sol](./src/distributions/ArguableVotingTournament.sol) is used to distribute governance tokens to the participants of the MAO by conducting autonomous competence identification tournaments.
[ArguableVotingTournament.sol](./src/distributions/ArguableVotingTournament.sol) implements a sophisticated tournament system for autonomous competence identification. It uses the Diamond pattern to provide a modular and upgradeable smart contract architecture.

This distribution deploys the Diamond Proxy that contains the following facets:
#### Core Components

- [EIP712InspectorFacet](./src/facets/EIP712InspectorFacet.sol) - Facet that contains the main logic of the distribution.
- [RankifyInstanceMainFacet](./src/facets//RankifyInstanceMainFacet.sol) - Facet that contains the main logic of the distribution.
- [RankifyGameMastersFacetFacet](./src/facets/RankifyInstanceGameMastersFacet.sol) - Facet that contains the main logic of the distribution.
- [RankifyInstanceGameOwnersFacet](./src/facets/RankifyInstanceGameOwnersFacet.sol) - Facet that contains the ownable logic of the distribution. (NB this will be deprecated)
- [RankifyInstanceRequirementsFacet](./src/facets/RankifyInstanceRequirementsFacet.sol) - Facet that contains the requirements logic of the distribution.
The distribution deploys a Diamond Proxy with the following facets:

To understand how it works further please refer to [docs.rankify.it](https://docs.rankify.it/governance) or ask us a question in [Discord](https://discord.gg/EddGgGUuWC)
- **EIP712InspectorFacet**: Handles message signing and verification using EIP-712 standard
- **RankifyInstanceMainFacet**: Core tournament logic including game creation, joining, and management
- **RankifyInstanceGameMastersFacet**: Manages voting and proposal submission mechanics
- **RankifyInstanceRequirementsFacet**: Handles participation requirements and constraints
- **DiamondLoupeFacet**: Standard Diamond pattern implementation for facet introspection
- **OwnershipFacet**: Manages contract ownership and permissions

#### Key Features

- Turn-based game mechanics with voting and proposal systems
- EIP-712 compliant message signing for secure interactions
- Modular architecture allowing for future upgrades
- Built-in reentrancy protection
- Integrated with the Rankify protocol for rank token management

## Contributing

Expand Down
30 changes: 0 additions & 30 deletions deploy/06_deoployMultipass.ts

This file was deleted.

39 changes: 17 additions & 22 deletions deploy/mao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { activeContractsList } from '@aragon/osx-ethers';
import { CodeIndex } from '@peeramid-labs/eds/types';
import CodeIndexAbi from '@peeramid-labs/eds/abi/src/CodeIndex.sol/CodeIndex.json';
import { MintSettingsStruct } from '../types/src/tokens/DistributableGovernanceERC20.sol/DistributableGovernanceERC20';
import { ArguableVotingTournament } from '../types/src/distributions/ArguableVotingTournament';
const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const { deployments, getNamedAccounts } = hre;
const { deploy } = deployments;
Expand All @@ -17,7 +18,7 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
console.log('network', network, process.env.NODE_ENV);
}
if (!network) throw new Error('Network not provided');
const { deployer } = await getNamedAccounts();
const { deployer, DAO } = await getNamedAccounts();
const codeIndexContract = (await ethers.getContractAt(
CodeIndexAbi,
'0xc0D31d398c5ee86C5f8a23FA253ee8a586dA03Ce',
Expand Down Expand Up @@ -101,34 +102,26 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
skipIfAlreadyDeployed: true,
});

const RankifyOwnerFacetDeployment = await deploy('RankifyInstanceGameOwnersFacet', {
from: deployer,
skipIfAlreadyDeployed: true,
});

const OwnershipFacetDeployment = await deploy('OwnershipFacet', {
from: deployer,
skipIfAlreadyDeployed: true,
});

const addresses: ArguableVotingTournament.ArguableTournamentAddressesStruct = {
loupeFacet: loupeFacetDeployment.address,
inspectorFacet: inspectorFacetDeployment.address,
RankifyMainFacet: RankifyMainFacetDeployment.address,
RankifyReqsFacet: RankifyReqsFacetDeployment.address,
RankifyGMFacet: RankifyGMFacetDeployment.address,
OwnershipFacet: OwnershipFacetDeployment.address,
};

const arguableVotingTournamentDeployment = await deploy('ArguableVotingTournament', {
from: deployer,
gasLimit: 8000000,
estimatedGasLimit: 8000000,
skipIfAlreadyDeployed: true,
args: [
initializerAdr,
initializerSelector,
distributionName,
version,
loupeFacetDeployment.address,
inspectorFacetDeployment.address,
RankifyMainFacetDeployment.address,
RankifyReqsFacetDeployment.address,
RankifyGMFacetDeployment.address,
RankifyOwnerFacetDeployment.address,
OwnershipFacetDeployment.address,
],
args: [initializerAdr, initializerSelector, distributionName, version, addresses],
});
const arguableVotingTournamentDeploymentCode = await hre.ethers.provider.getCode(
arguableVotingTournamentDeployment.address,
Expand Down Expand Up @@ -160,14 +153,16 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
}
const govTokenCode = await hre.ethers.provider.getCode(govTokenDeployment.address);
const govTokenCodeId = ethers.utils.keccak256(govTokenCode);

const rankifyToken = await deployments.get('Rankify');
const result = await deploy('MAODistribution', {
from: deployer,
skipIfAlreadyDeployed: true,
args: [
_tokenVotingPluginRepo,
_daoFactory,
_trustedForwarder,
rankifyToken.address,
DAO,
rankTokenCodeId,
arguableVotingTournamentCodeId,
accessManagerId,
Expand All @@ -183,8 +178,8 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
if (MaoDistrCodeIdAddress === ethers.constants.AddressZero) {
await codeIndexContract.register(result.address);
}
const code = await hre.ethers.provider.getCode(result.address);
const codeId = ethers.utils.keccak256(code);
// const code = await hre.ethers.provider.getCode(result.address);
// const codeId = ethers.utils.keccak256(code);
// console.log('MAO deployed at', result.address, 'codeId', codeId);
return;
};
Expand Down
4 changes: 3 additions & 1 deletion deploy/sacm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const codeId = ethers.utils.keccak256(code);
const registerAddress = await codeIndexContract.get(codeId);
if (registerAddress == ethers.constants.AddressZero) {
console.warn('registering contract', registerAddress, sacmDeployment.address, codeId);
if (process.env.NODE_ENV !== 'TEST') {
console.warn('registering contract', registerAddress, sacmDeployment.address, codeId);
}
await codeIndexContract.register(sacmDeployment.address);
}
};
Expand Down
66 changes: 37 additions & 29 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import 'hardhat-contract-sizer';
import 'hardhat-deploy';
import 'hardhat-tracer';
import 'solidity-docgen';
import './playbook/initializeDomain';
import './playbook/createGame';
import getSuperInterface from './scripts/getSuperInterface';
import { ErrorFragment, EventFragment, FunctionFragment } from '@ethersproject/abi';

task('accounts', 'Prints the list of accounts', async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
Expand All @@ -23,19 +24,28 @@ task('accounts', 'Prints the list of accounts', async (taskArgs, hre) => {
}
});

// task("upload2IPFS", "Uploads files to ipfs")
// .addParam("path", "file path")
// .setAction(async (taskArgs) => {
// const data = fs.readFileSync(taskArgs.path);
// await ipfsUtils.upload2IPFS(data);
// });

// task("uploadDir2IPFS", "Uploads directory to ipfs")
// .addParam("path", "path")
// .setAction(async (taskArgs) => {
// await ipfsUtils.uploadDir2IPFS(taskArgs.path);
// });

task('getSuperInterface', 'Prints the super interface of a contract').setAction(async (taskArgs, hre) => {
const su = getSuperInterface();
let return_value: {
functions: Record<string, FunctionFragment>;
events: Record<string, EventFragment>;
errors: Record<string, ErrorFragment>;
} = {
functions: {},
events: {},
errors: {},
};
Object.values(su.functions).forEach(x => {
return_value['functions'][su.getSighash(x)] = x;
});
Object.values(su.events).forEach(x => {
return_value['events'][su.getSighash(x)] = x;
});
Object.values(su.errors).forEach(x => {
return_value['errors'][su.getSighash(x)] = x;
});
console.log(JSON.stringify(return_value, null, 2));
});
task('replaceFacet', 'Upgrades facet')
.addParam('facet', 'facet')
.addParam('address', 'contract address')
Expand Down Expand Up @@ -99,6 +109,9 @@ export default {
defaultPlayer: {
localhost: '0xF52E5dF676f51E410c456CC34360cA6F27959420',
},
DAO: {
default: '0x520E00225C4a43B6c55474Db44a4a44199b4c3eE',
},
},
mocha: {
timeout: 400000,
Expand Down Expand Up @@ -148,6 +161,15 @@ export default {
},
solidity: {
compilers: [
{
version: '0.8.28',
settings: {
optimizer: {
enabled: true,
runs: 2000,
},
},
},
{
version: '0.8.20',
settings: {
Expand All @@ -169,19 +191,6 @@ export default {
],
},
diamondAbi: [
{
// (required) The name of your Diamond ABI
name: 'MultipassDiamond',
include: ['DNSFacet', 'OwnershipFacet', 'DiamondLoupeFacet', 'EIP712InspectorFacet'],
// We explicitly set `strict` to `true` because we want to validate our facets don't accidentally provide overlapping functions
strict: true,
// We use our diamond utils to filter some functions we ignore from the combined ABI
filter(abiElement: unknown, index: number, abi: unknown[], fullyQualifiedName: string) {
// const changes = new diamondUtils.DiamondChanges();
const signature = toSignature(abiElement);
return isIncluded(fullyQualifiedName, signature);
},
},
{
name: 'RankifyDiamondInstance',
include: [
Expand All @@ -191,9 +200,8 @@ export default {
'RankifyInstanceMainFacet',
'RankifyInstanceRequirementsFacet',
'RankifyInstanceGameMastersFacet',
'RankifyInstanceGameOwnersFacet',
],
strict: true,
strict: false,
filter(abiElement: unknown, index: number, abi: unknown[], fullyQualifiedName: string) {
const signature = toSignature(abiElement);
return isIncluded(fullyQualifiedName, signature);
Expand Down
Loading

0 comments on commit c53987d

Please sign in to comment.