Skip to content

Commit ae8a19d

Browse files
author
0xleal
authored
Add FixedBuilderScore contract and deployment script (#127)
Replace PassportBuilderScore with a dummy contract that returns a fixed score for all users. Admin can update the score via setFixedScore(). Includes deployment script that also updates TalentVault to use the new contract.
1 parent 87af96c commit ae8a19d

5 files changed

Lines changed: 212 additions & 0 deletions

File tree

‎.tool-versions‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
nodejs 20.12.2
22
solidity 0.8.24
3+
yarn 1.22.19
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import "@openzeppelin/contracts/access/Ownable.sol";
5+
6+
/// @title Fixed Builder Score
7+
/// @notice A dummy PassportBuilderScore that returns a fixed score for all users
8+
contract FixedBuilderScore is Ownable {
9+
uint256 public fixedScore;
10+
11+
event FixedScoreUpdated(uint256 oldScore, uint256 newScore);
12+
13+
constructor(uint256 _fixedScore, address _owner) Ownable(_owner) {
14+
fixedScore = _fixedScore;
15+
}
16+
17+
/// @notice Set the fixed score returned for all passport IDs
18+
/// @param _fixedScore The new fixed score
19+
function setFixedScore(uint256 _fixedScore) external onlyOwner {
20+
uint256 oldScore = fixedScore;
21+
fixedScore = _fixedScore;
22+
emit FixedScoreUpdated(oldScore, _fixedScore);
23+
}
24+
25+
/// @notice Returns the fixed score for any passport ID
26+
/// @param _passportId Ignored - returns the same score for all
27+
/// @return The fixed score
28+
function getScore(uint256 _passportId) external view returns (uint256) {
29+
return fixedScore;
30+
}
31+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ethers, network } from "hardhat";
2+
3+
const TALENT_VAULT_MAINNET = "0x23Ff3256A29847d7EF760943bd6679b565CbdE5a";
4+
5+
// Set to 60 to give everyone the bonus yield rate, or 0 for base rate only
6+
const INITIAL_FIXED_SCORE = 60;
7+
8+
async function main() {
9+
const isMainnet = network.name === "mainnet" || network.name === "base";
10+
11+
if (!isMainnet) {
12+
return;
13+
}
14+
15+
const talentVaultAddress = TALENT_VAULT_MAINNET;
16+
17+
console.log(`Deploying Fixed Builder Score at ${network.name}`);
18+
19+
const [admin] = await ethers.getSigners();
20+
21+
console.log(`Admin/Owner will be ${admin.address}`);
22+
23+
// Deploy FixedBuilderScore
24+
const fixedBuilderScoreContract = await ethers.getContractFactory("FixedBuilderScore");
25+
const fixedBuilderScore = await fixedBuilderScoreContract.deploy(INITIAL_FIXED_SCORE, admin.address);
26+
await fixedBuilderScore.deployed();
27+
28+
console.log(`Fixed Builder Score deployed at ${fixedBuilderScore.address}`);
29+
console.log(`Initial fixed score: ${INITIAL_FIXED_SCORE}`);
30+
console.log(`Owner: ${admin.address}`);
31+
32+
// Update TalentVault to use the new FixedBuilderScore
33+
console.log(`\nUpdating TalentVault at ${talentVaultAddress}...`);
34+
const talentVault = await ethers.getContractAt("TalentVault", talentVaultAddress);
35+
const tx = await talentVault.setPassportBuilderScore(fixedBuilderScore.address);
36+
await tx.wait();
37+
38+
console.log(`TalentVault.setPassportBuilderScore updated to ${fixedBuilderScore.address}`);
39+
40+
console.log("\n--- Verification Command ---");
41+
console.log(
42+
`npx hardhat verify --network ${network.name} ${fixedBuilderScore.address} ${INITIAL_FIXED_SCORE} ${admin.address}`
43+
);
44+
45+
console.log("\nDone");
46+
}
47+
48+
main()
49+
.then(() => process.exit(0))
50+
.catch((error) => {
51+
console.error(error);
52+
process.exit(1);
53+
});

‎test/contracts/talent/TalentVault.ts‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
PassportRegistry,
1010
PassportBuilderScore,
1111
PassportWalletRegistry,
12+
FixedBuilderScore,
1213
} from "../../../typechain-types";
1314
import { Artifacts } from "../../shared";
1415
import { ensureTimestamp } from "../../shared/utils";
@@ -942,4 +943,128 @@ describe("TalentVault", () => {
942943
});
943944
});
944945
});
946+
947+
describe("FixedBuilderScore", async () => {
948+
let fixedBuilderScore: FixedBuilderScore;
949+
950+
beforeEach(async () => {
951+
fixedBuilderScore = (await deployContract(admin, Artifacts.FixedBuilderScore, [
952+
60,
953+
admin.address,
954+
])) as FixedBuilderScore;
955+
});
956+
957+
describe("Deployment", async () => {
958+
it("Should set the correct owner", async () => {
959+
expect(await fixedBuilderScore.owner()).to.equal(admin.address);
960+
});
961+
962+
it("Should set the correct initial fixed score", async () => {
963+
expect(await fixedBuilderScore.fixedScore()).to.equal(60);
964+
});
965+
});
966+
967+
describe("#getScore", async () => {
968+
it("returns the fixed score for any passport ID", async () => {
969+
expect(await fixedBuilderScore.getScore(1)).to.equal(60);
970+
expect(await fixedBuilderScore.getScore(999)).to.equal(60);
971+
expect(await fixedBuilderScore.getScore(0)).to.equal(60);
972+
});
973+
});
974+
975+
describe("#setFixedScore", async () => {
976+
context("when called by the owner", async () => {
977+
it("updates the fixed score", async () => {
978+
await fixedBuilderScore.setFixedScore(80);
979+
expect(await fixedBuilderScore.fixedScore()).to.equal(80);
980+
});
981+
982+
it("emits FixedScoreUpdated event", async () => {
983+
await expect(fixedBuilderScore.setFixedScore(80))
984+
.to.emit(fixedBuilderScore, "FixedScoreUpdated")
985+
.withArgs(60, 80);
986+
});
987+
});
988+
989+
context("when called by a non-owner", async () => {
990+
it("reverts", async () => {
991+
await expect(fixedBuilderScore.connect(user1).setFixedScore(80)).to.be.revertedWith(
992+
`OwnableUnauthorizedAccount("${user1.address}")`
993+
);
994+
});
995+
});
996+
});
997+
998+
describe("TalentVault with FixedBuilderScore", async () => {
999+
beforeEach(async () => {
1000+
// Swap PassportBuilderScore for FixedBuilderScore in TalentVault
1001+
await talentVault.setPassportBuilderScore(fixedBuilderScore.address);
1002+
});
1003+
1004+
it("uses the fixed score for yield calculation", async () => {
1005+
expect(await talentVault.passportBuilderScore()).to.equal(fixedBuilderScore.address);
1006+
});
1007+
1008+
context("when fixed score is >= 60 (bonus tier)", async () => {
1009+
it("calculates rewards at 10% APY", async () => {
1010+
await fixedBuilderScore.setFixedScore(60);
1011+
1012+
const depositAmount = ethers.utils.parseEther("1000");
1013+
await talentToken.transfer(user1.address, depositAmount);
1014+
await talentToken.connect(user1).approve(talentVault.address, depositAmount);
1015+
await talentVault.connect(user1).deposit(depositAmount, user1.address);
1016+
1017+
// Simulate time passing
1018+
ensureTimestamp(currentDateEpochSeconds + 31536000); // 1 year ahead
1019+
1020+
await talentVault.connect(user1).refresh();
1021+
1022+
// 10% yield over 90 days (yieldAccrualDeadline)
1023+
const expectedRewards = yieldBasePerDay.mul(2).mul(90);
1024+
const userBalance = await talentVault.balanceOf(user1.address);
1025+
expect(userBalance).to.be.closeTo(depositAmount.add(expectedRewards), ethers.utils.parseEther("0.1"));
1026+
});
1027+
});
1028+
1029+
context("when fixed score is < 60 (base tier)", async () => {
1030+
it("calculates rewards at 5% APY", async () => {
1031+
await fixedBuilderScore.setFixedScore(59);
1032+
1033+
const depositAmount = ethers.utils.parseEther("1000");
1034+
await talentToken.transfer(user1.address, depositAmount);
1035+
await talentToken.connect(user1).approve(talentVault.address, depositAmount);
1036+
await talentVault.connect(user1).deposit(depositAmount, user1.address);
1037+
1038+
// Simulate time passing
1039+
ensureTimestamp(currentDateEpochSeconds + 31536000); // 1 year ahead
1040+
1041+
await talentVault.connect(user1).refresh();
1042+
1043+
// 5% yield over 90 days (yieldAccrualDeadline)
1044+
const expectedRewards = yieldBasePerDay.mul(90);
1045+
const userBalance = await talentVault.balanceOf(user1.address);
1046+
expect(userBalance).to.be.closeTo(depositAmount.add(expectedRewards), ethers.utils.parseEther("0.1"));
1047+
});
1048+
});
1049+
1050+
context("when admin changes fixed score", async () => {
1051+
it("affects future yield calculations for all users", async () => {
1052+
// Start with score 0 (base rate)
1053+
await fixedBuilderScore.setFixedScore(0);
1054+
1055+
const depositAmount = ethers.utils.parseEther("1000");
1056+
await talentToken.transfer(user1.address, depositAmount);
1057+
await talentToken.connect(user1).approve(talentVault.address, depositAmount);
1058+
await talentVault.connect(user1).deposit(depositAmount, user1.address);
1059+
1060+
// Change to bonus tier
1061+
await fixedBuilderScore.setFixedScore(60);
1062+
1063+
// All users now get the bonus rate
1064+
const yieldRate = await talentVault.getYieldRateForScore(user1.address);
1065+
expect(yieldRate).to.equal(10_00); // 10%
1066+
});
1067+
});
1068+
});
1069+
});
9451070
});

‎test/shared/artifacts.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import TalentVault from "../../artifacts/contracts/talent/TalentVault.sol/Talent
1515
import TalentVaultV2 from "../../artifacts/contracts/talent/TalentVaultV2.sol/TalentVaultV2.json";
1616
import BaseAPY from "../../artifacts/contracts/talent/vault-options/BaseAPY.sol/BaseAPY.json";
1717
import MultiSendETH from "../../artifacts/contracts/utils/MultiSendETH.sol/MultiSendETH.json";
18+
import FixedBuilderScore from "../../artifacts/contracts/passport/FixedBuilderScore.sol/FixedBuilderScore.json";
1819

1920
export {
2021
PassportRegistry,
@@ -34,4 +35,5 @@ export {
3435
TalentVaultV2,
3536
BaseAPY,
3637
MultiSendETH,
38+
FixedBuilderScore,
3739
};

0 commit comments

Comments
 (0)