Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
88 changes: 88 additions & 0 deletions contracts/passport/StakingPassport.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./PassportRegistry.sol";

contract StakingPassport is Ownable, ReentrancyGuard {
PassportRegistry public passportRegistry;
uint256 public minimumStakeTime = 60 days;
uint256 public minimumSlashTime = 30 days;

struct StakeData {
address staker;
uint256 passportId;
uint256 startStakeTime;
uint256 unlockStakeTime;
uint256 amount;
uint256 retrievedAmount;
uint256 slashedAmount;
string slashedReason;
}

mapping(address => mapping(uint256 => StakeData)) public stakes;

event Stake(address origin, uint256 passportId, uint256 amount);
event Unstake(address origin, uint256 passportId, uint256 amount);
event Slash(address staker, uint256 passportId, uint256 amount, string reason);

constructor(address _passportRegistryAddress) Ownable(msg.sender) {
passportRegistry = PassportRegistry(_passportRegistryAddress);
}

function stake(uint256 _passportId) payable public {
require(passportRegistry.idPassport(_passportId) != address(0), "Passport ID does not exist");
require(msg.value > 0, "Must stake an amount higher than 0");

StakeData storage stakeData = stakes[msg.sender][_passportId];
stakeData.staker = msg.sender;
stakeData.passportId = _passportId;
stakeData.startStakeTime = block.timestamp;
stakeData.unlockStakeTime = block.timestamp + minimumStakeTime;
stakeData.amount = stakeData.amount + msg.value;

emit Stake(msg.sender, _passportId, msg.value);
}

function unstake(uint256 _passportId, uint256 amount) public {
StakeData storage existingStake = stakes[msg.sender][_passportId];
uint256 amountAvailableToRetrieve = existingStake.amount - existingStake.retrievedAmount - existingStake.slashedAmount;
require(amountAvailableToRetrieve > 0, "Stake must have funds to retrieve");
require(amount <= amountAvailableToRetrieve, "You cannot withdraw more than you have staked");
require(existingStake.unlockStakeTime < block.timestamp, "Your stake is not unlocked");

existingStake.retrievedAmount = existingStake.retrievedAmount + amount;
payable(msg.sender).transfer(amount);

if(existingStake.retrievedAmount == existingStake.amount) {
delete stakes[msg.sender][_passportId];
}

emit Unstake(msg.sender, _passportId, amount);
}

function slash(uint256 _passportId, uint256[] calldata amounts, address[] calldata stakers, string calldata reason) public onlyOwner {
require(amounts.length == stakers.length, "the size of the stakers must be the same as the size of amounts");

for (uint256 i = 0; i < amounts.length; i++) {
uint256 amount = amounts[i];
address staker = stakers[i];
_slash(_passportId, amount, staker, reason);
}
}

function _slash(uint256 _passportId, uint256 amount, address staker, string calldata reason) private {
StakeData storage existingStake = stakes[staker][_passportId];
uint256 amountAvailableToSlash = existingStake.amount - existingStake.retrievedAmount - existingStake.slashedAmount;
require(amountAvailableToSlash > 0, "There is nothing left to slash");
require(amountAvailableToSlash >= amount, "You can't slash that much");
require(existingStake.startStakeTime + minimumSlashTime >= block.timestamp, "Not enough time has passed to slash this stake");
// require(reason != "", "You must name a reason to slash");

existingStake.slashedAmount = existingStake.slashedAmount + amount;
payable(msg.sender).transfer(amount);

emit Slash(staker, _passportId, amount, reason);
}
}
27 changes: 21 additions & 6 deletions scripts/passport/deployScorer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ethers, network } from "hardhat";

import { deployPassportBuilderScore } from "../shared";
import { deployPassportBuilderScore, deployPassportSources, deploySmartScorer } from "../shared";

async function main() {
console.log(`Deploying passport builder score at ${network.name}`);
Expand All @@ -9,11 +9,26 @@ async function main() {

console.log(`Admin will be ${admin.address}`);

// @TODO: Replace with registry address
const passport = await deployPassportBuilderScore("0x0", admin.address);

console.log(`Scorer Address: ${passport.address}`);
console.log(`Scorer owner: ${await passport.owner()}`);
// base sepolia: 0xa600b3356c1440b6d6e57b0b7862dc3dfb66bc43
// base mainnet: 0xb477A9BD2547ad61f4Ac22113172Dd909E5B2331
const passportRegistry = "0xa600b3356c1440b6d6e57b0b7862dc3dfb66bc43";

const scorer = await deployPassportBuilderScore(passportRegistry, admin.address);
console.log(`Scorer Address: ${scorer.address}`);
console.log(`Scorer owner: ${await scorer.owner()}`);

const sources = await deployPassportSources(admin.address);
console.log(`Sources Address: ${sources.address}`);
console.log(`Sources owner: ${await sources.owner()}`);

const smartScorer = await deploySmartScorer(
admin.address,
scorer.address,
sources.address,
passportRegistry,
admin.address
);
console.log(`Smart Scorer Address: ${smartScorer.address}`);

console.log("Done");
}
Expand Down
36 changes: 34 additions & 2 deletions scripts/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type {
TalentRewardClaim,
PassportBuilderScore,
TalentCommunitySale,
PassportSources,
SmartBuilderScore,
} from "../../typechain-types";

export async function deployPassport(owner: string): Promise<PassportRegistry> {
Expand All @@ -17,10 +19,10 @@ export async function deployPassport(owner: string): Promise<PassportRegistry> {
return deployedPassport as PassportRegistry;
}

export async function deployTalentToken(): Promise<TalentProtocolToken> {
export async function deployTalentToken(owner: string): Promise<TalentProtocolToken> {
const talentTokenContract = await ethers.getContractFactory("TalentProtocolToken");

const deployedTalentToken = await talentTokenContract.deploy();
const deployedTalentToken = await talentTokenContract.deploy(owner);
await deployedTalentToken.deployed();

return deployedTalentToken as TalentProtocolToken;
Expand Down Expand Up @@ -55,6 +57,36 @@ export async function deployPassportBuilderScore(registry: string, owner: string
return deployedPassportBuilderScore as PassportBuilderScore;
}

export async function deployPassportSources(owner: string): Promise<PassportSources> {
const passportSources = await ethers.getContractFactory("PassportSources");

const deployedPassportSources = await passportSources.deploy(owner);
await deployedPassportSources.deployed();

return deployedPassportSources as PassportSources;
}

export async function deploySmartScorer(
owner: string,
scorer: string,
sources: string,
registry: string,
feeCollector: string
): Promise<SmartBuilderScore> {
const smartBuilderScoreContract = await ethers.getContractFactory("SmartBuilderScore");

const deployedSmartBuilderScore = await smartBuilderScoreContract.deploy(
owner,
scorer,
sources,
registry,
feeCollector
);
await deployedSmartBuilderScore.deployed();

return deployedSmartBuilderScore as SmartBuilderScore;
}

export async function deployTalentCommunitySale(
owner: string,
tokenAddress: string,
Expand Down
17 changes: 10 additions & 7 deletions scripts/talent/deployTalentToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { deployTalentToken } from "../shared";
const talentTokenSetup = [
{
name: "SAFE Talent Token",
address: "0x0",
amount: "1000000000"
}
]
address: "0x33041027dd8F4dC82B6e825FB37ADf8f15d44053",
amount: "1000000000",
},
];

async function main() {
console.log(`Deploying Talent Token at ${network.name}`);
Expand All @@ -18,7 +18,7 @@ async function main() {

console.log(`Admin will be ${admin.address}`);

console.log('validating setup');
console.log("validating setup");

for (let i = 0; i < talentTokenSetup.length; i++) {
const setup = talentTokenSetup[i];
Expand All @@ -27,12 +27,15 @@ async function main() {
}
}

const totalAmount = talentTokenSetup.reduce((acc, setup) => acc.add(ethers.utils.parseEther(setup.amount)), BigNumber.from(0));
const totalAmount = talentTokenSetup.reduce(
(acc, setup) => acc.add(ethers.utils.parseEther(setup.amount)),
BigNumber.from(0)
);
if (!totalAmount.eq(ethers.utils.parseEther("1000000000"))) {
throw new Error(`Total amount does not match the full supply`);
}

const talentToken = await deployTalentToken();
const talentToken = await deployTalentToken(admin.address);

console.log(`Talent Token Address: ${talentToken.address}`);
console.log(`Talent Token owner: ${await talentToken.owner()}`);
Expand Down