From a0e7442f45871055aa205eb51062869f266d4344 Mon Sep 17 00:00:00 2001 From: twx-pathdao <97669360+twx-pathdao@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:48:12 +0800 Subject: [PATCH 1/4] feat: modified testing to guarantee output amounts (#26) --- test/rewardsV2.js | 63 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/test/rewardsV2.js b/test/rewardsV2.js index 32707a2..f8ec542 100644 --- a/test/rewardsV2.js +++ b/test/rewardsV2.js @@ -556,7 +556,9 @@ describe("RewardsV2", function () { const app3 = await createApplication(base, founder, 2); const agent3 = await createAgent(base, app3); const app4 = await createApplication(base, founder, 3); - const agent4 = await createAgent(base, app4); + const agent4 = await createAgent(base, app4); + const lp3 = await rewards.getLPValue(agent3[0]); + console.log("Initial LP ", lp3); // Create contributions for all 4 virtuals for (let i = 1; i <= 4; i++) { @@ -577,7 +579,7 @@ describe("RewardsV2", function () { 0, `Test ${i}`, base, - contributor1.address, + trader.address, [validator1] ); } @@ -586,32 +588,53 @@ describe("RewardsV2", function () { "IUniswapV2Router02", process.env.UNISWAP_ROUTER ); - // Trade on different LP - await virtualToken.mint(trader.address, parseEther("300")); + // Add liquidity of 0.25, 0.75 and 1 + await virtualToken.mint(trader.address, parseEther("400")); await virtualToken .connect(trader) - .approve(router.target, parseEther("300")); + .approve(router.target, parseEther("400")); for (let i of [1, 3, 4]) { const agentTokenAddr = (await agentNft.virtualInfo(i)).token; - const amountToBuy = parseEther((20 * i).toString()); - const capital = parseEther("100"); + const agentToken = await ethers.getContractAt("AgentToken", agentTokenAddr); + await agentToken.connect(trader).approve(router.target, parseEther("400")) + const amountToAdd = parseEther((0.25 * i).toString()); + const capital = parseEther("400"); await router .connect(trader) - .swapTokensForExactTokens( - amountToBuy, - capital, - [virtualToken.target, agentTokenAddr], + .addLiquidity( + virtualToken.target, + agentTokenAddr, + amountToAdd, + amountToAdd, + amountToAdd, + amountToAdd, trader.address, Math.floor(new Date().getTime() / 1000 + 6000000) ); + const lp_after_buy = await rewards.getLPValue(i); + console.log("lp after buy LP ", lp_after_buy); await mine(1); } // Distribute rewards // Expectations: - // virtual 4>3>1 + // virtual 4 = 100001000000000000000000n, + // virtual 3 = 100000750000000000000000n, + // virtual 1 = 100000250000000000000000n // virtual 2 = 0 + + // Using gwei as calculation size due to BigInt limitations const rewardSize = 300000; + const Lp4 = BigInt(100001000000000); + const Lp3 = BigInt(100000750000000); + const Lp1 = BigInt(100000250000000); + const rewardSizeInGwei = BigInt(rewardSize * 10 ** 9) + + + const totalLp = Lp1 + Lp3 + Lp4 + console.log("totallp", totalLp) + + await virtualToken.approve( rewards.target, parseEther(rewardSize.toString()) @@ -626,6 +649,11 @@ describe("RewardsV2", function () { founder.address, [1] ); + + //99999583336111092592716 + const rewardSizeAfterProtocol = rewardSizeInGwei * BigInt(process.env.STAKER_SHARES) / BigInt(10000) + const expectedRewardLP1Ratio = await (rewardSizeAfterProtocol * Lp1) / totalLp + const rewards2 = await rewards.getTotalClaimableStakerRewards( founder.address, [2] @@ -634,10 +662,21 @@ describe("RewardsV2", function () { founder.address, [3] ); + const expectedRewardLP3Ratio = await (rewardSizeAfterProtocol * Lp3) / totalLp + + const rewards4 = await rewards.getTotalClaimableStakerRewards( founder.address, [4] ); + const expectedRewardLP4Ratio = (rewardSizeAfterProtocol * Lp4) / totalLp + + + expect(await parseInt(ethers.formatUnits(rewards1.toString(), "gwei"))).to.be.equal(expectedRewardLP1Ratio); + expect(await parseInt(ethers.formatUnits(rewards3.toString(), "gwei"))).to.be.equal(expectedRewardLP3Ratio); + expect(await parseInt(ethers.formatUnits(rewards4.toString(), "gwei"))).to.be.equal(expectedRewardLP4Ratio); + + expect(rewards4).to.be.greaterThan(rewards3); expect(rewards3).to.be.greaterThan(rewards1); expect(rewards2).to.be.equal(0n); From 62f9269d283134b74e520adfc4ea9f362719ae7b Mon Sep 17 00:00:00 2001 From: kw Date: Tue, 18 Jun 2024 11:52:37 +0800 Subject: [PATCH 2/4] auto execute proposal when forVotes == totalSupply --- contracts/virtualPersona/AgentDAO.sol | 14 ++++++ test/agentDAO.js | 71 +++++---------------------- 2 files changed, 27 insertions(+), 58 deletions(-) diff --git a/contracts/virtualPersona/AgentDAO.sol b/contracts/virtualPersona/AgentDAO.sol index 90fa679..1b86ee5 100644 --- a/contracts/virtualPersona/AgentDAO.sol +++ b/contracts/virtualPersona/AgentDAO.sol @@ -176,9 +176,23 @@ contract AgentDAO is } } + if (support == 1) { + _tryAutoExecute(proposalId); + } + return weight; } + // Auto execute when forVotes == totalSupply + function _tryAutoExecute(uint256 proposalId) internal { + (, uint256 forVotes, ) = proposalVotes(proposalId); + if ( + forVotes == token().getPastTotalSupply(proposalSnapshot(proposalId)) + ) { + execute(proposalId); + } + } + function _updateMaturity( address account, uint256 proposalId, diff --git a/test/agentDAO.js b/test/agentDAO.js index 09c491f..72cbf89 100644 --- a/test/agentDAO.js +++ b/test/agentDAO.js @@ -17,7 +17,7 @@ const { parseEther, formatEther } = require("ethers"); const getExecuteCallData = (factory, proposalId) => { return factory.interface.encodeFunctionData("executeApplication", [ proposalId, - false + false, ]); }; @@ -197,7 +197,9 @@ describe("AgentDAO", function () { const { agentFactory, applicationId } = base; const { founder } = await getAccounts(); - await agentFactory.connect(founder).executeApplication(applicationId, false); + await agentFactory + .connect(founder) + .executeApplication(applicationId, false); const factoryFilter = agentFactory.filters.NewPersona; const factoryEvents = await agentFactory.queryFilter(factoryFilter, -1); @@ -218,62 +220,9 @@ describe("AgentDAO", function () { }; } - async function createContribution( - coreId, - maturity, - parentId, - isModel, - datasetId, - desc, - base, - account - ) { - const { founder } = await getAccounts(); - const { agent, serviceNft, contributionNft, minter } = base; - const agentDAO = await ethers.getContractAt("AgentDAO", agent.dao); - - const descHash = getDescHash(desc); - - const mintCalldata = await getMintServiceCalldata( - serviceNft, - agent.virtualId, - descHash - ); - - await agentDAO.propose([serviceNft.target], [0], [mintCalldata], desc); - const filter = agentDAO.filters.ProposalCreated; - const events = await agentDAO.queryFilter(filter, -1); - const event = events[0]; - const proposalId = event.args[0]; - - await contributionNft.mint( - account, - agent.virtualId, - coreId, - TOKEN_URI, - proposalId, - parentId, - isModel, - datasetId - ); - - const voteParams = isModel - ? abi.encode(["uint256", "uint8[] memory"], [maturity, [0, 1, 1, 0, 2]]) - : "0x"; - await agentDAO - .connect(founder) - .castVoteWithReasonAndParams(proposalId, 1, "lfg", voteParams); - await mine(600); - - await agentDAO.execute(proposalId); - await minter.mint(proposalId); - - return proposalId; - } - before(async function () {}); - it("should allow early execution when forVotes == totalSupply", async function () { + it("should auto early execution when forVotes == totalSupply", async function () { const base = await loadFixture(deployWithAgent); const { founder, deployer } = await getAccounts(); const { @@ -300,11 +249,17 @@ describe("AgentDAO", function () { const event = events[0]; const proposalId = event.args[0]; + // Proposal not executed yet + await expect(serviceNft.ownerOf(proposalId)).to.be.reverted; + await mine(10); await agentDAO.castVoteWithReasonAndParams(proposalId, 1, "lfg", "0x"); + + // Proposal should be auto executed + expect(await serviceNft.ownerOf(proposalId)).to.equal(agent.tba); const state = await agentDAO.state(proposalId); - expect(state).to.equal(4n); - await expect(agentDAO.execute(proposalId)).to.not.rejected; + expect(state).to.equal(7n); + await expect(agentDAO.execute(proposalId)).to.be.reverted; }); it("should not allow early execution when forVotes < totalSupply although met quorum", async function () { From 580e64240400c4f829e2953790c35156df5cb450 Mon Sep 17 00:00:00 2001 From: kw Date: Tue, 18 Jun 2024 13:52:03 +0800 Subject: [PATCH 3/4] updated delegate test cases to match v2 changes --- test/delegate.js | 233 ++++++++++++++++++++++++++--------------------- 1 file changed, 127 insertions(+), 106 deletions(-) diff --git a/test/delegate.js b/test/delegate.js index 478de5f..2944abc 100644 --- a/test/delegate.js +++ b/test/delegate.js @@ -11,7 +11,7 @@ const { const getExecuteCallData = (factory, proposalId) => { return factory.interface.encodeFunctionData("executeApplication", [ proposalId, - false + false, ]); }; @@ -34,131 +34,150 @@ describe("Delegation", function () { daoThreshold: 1000000000000000000000n, }; - async function deployBaseContracts() { - const [deployer] = await ethers.getSigners(); - const veToken = await ethers.deployContract( - "veVirtualToken", - [deployer.address], - {} - ); - await veToken.waitForDeployment(); + const getAccounts = async () => { + const [deployer, ipVault, founder, poorMan, trader, treasury] = + await ethers.getSigners(); + return { deployer, ipVault, founder, poorMan, trader, treasury }; + }; - const demoToken = await ethers.deployContract( - "BMWToken", - [deployer.address], - {} - ); - await demoToken.waitForDeployment(); + async function deployBaseContracts() { + const { deployer, ipVault, treasury } = await getAccounts(); - const protocolDAO = await ethers.deployContract( - "VirtualProtocolDAO", - [veToken.target, 0, PROTOCOL_DAO_VOTING_PERIOD, PROPOSAL_THRESHOLD, 500], + const virtualToken = await ethers.deployContract( + "VirtualToken", + [PROPOSAL_THRESHOLD, deployer.address], {} ); - await protocolDAO.waitForDeployment(); + await virtualToken.waitForDeployment(); - const AgentNft = await ethers.getContractFactory("AgentNft"); - const personaNft = await upgrades.deployProxy(AgentNft, [deployer.address]); + const AgentNft = await ethers.getContractFactory("AgentNftV2"); + const agentNft = await upgrades.deployProxy(AgentNft, [deployer.address]); const contribution = await upgrades.deployProxy( await ethers.getContractFactory("ContributionNft"), - [personaNft.target], + [agentNft.target], {} ); const service = await upgrades.deployProxy( await ethers.getContractFactory("ServiceNft"), - [personaNft.target, contribution.target, 7000n], + [agentNft.target, contribution.target, process.env.DATASET_SHARES], {} ); - await personaNft.setContributionService( - contribution.target, - service.target - ); + await agentNft.setContributionService(contribution.target, service.target); - const personaToken = await ethers.deployContract("AgentToken"); - await personaToken.waitForDeployment(); - const personaDAO = await ethers.deployContract("AgentDAO"); - await personaDAO.waitForDeployment(); + // Implementation contracts + const agentToken = await ethers.deployContract("AgentToken"); + await agentToken.waitForDeployment(); + const agentDAO = await ethers.deployContract("AgentDAO"); + await agentDAO.waitForDeployment(); + const agentVeToken = await ethers.deployContract("AgentVeToken"); + await agentVeToken.waitForDeployment(); - const tba = await ethers.deployContract("ERC6551Registry"); - - const personaFactory = await upgrades.deployProxy( - await ethers.getContractFactory("AgentFactory"), + const agentFactory = await upgrades.deployProxy( + await ethers.getContractFactory("AgentFactoryV2"), [ - personaToken.target, - personaDAO.target, - tba.target, - demoToken.target, - personaNft.target, - parseEther("100000"), - 5, - protocolDAO.target, + agentToken.target, + agentVeToken.target, + agentDAO.target, + process.env.TBA_REGISTRY, + virtualToken.target, + agentNft.target, + PROPOSAL_THRESHOLD, deployer.address, ] ); - - await personaNft.grantRole( - await personaNft.MINTER_ROLE(), - personaFactory.target + await agentFactory.waitForDeployment(); + await agentNft.grantRole(await agentNft.MINTER_ROLE(), agentFactory.target); + const minter = await ethers.deployContract("Minter", [ + service.target, + contribution.target, + agentNft.target, + process.env.IP_SHARES, + process.env.IMPACT_MULTIPLIER, + ipVault.address, + agentFactory.target, + deployer.address, + ]); + await minter.waitForDeployment(); + await agentFactory.setMinter(minter.target); + await agentFactory.setMaturityDuration(86400 * 365 * 10); // 10years + await agentFactory.setUniswapRouter(process.env.UNISWAP_ROUTER); + await agentFactory.setTokenAdmin(deployer.address); + await agentFactory.setTokenSupplyParams( + process.env.AGENT_TOKEN_LIMIT, + process.env.AGENT_TOKEN_LIMIT, + process.env.BOT_PROTECTION ); - - await demoToken.mint(deployer.address, PROPOSAL_THRESHOLD); - await demoToken.approve(personaFactory.target, PROPOSAL_THRESHOLD); - - await personaFactory.proposePersona( - genesisInput.name, - genesisInput.symbol, - genesisInput.tokenURI, - genesisInput.cores, - genesisInput.tbaSalt, - genesisInput.tbaImplementation, - genesisInput.daoVotingPeriod, - genesisInput.daoThreshold + await agentFactory.setTokenTaxParams( + process.env.TAX, + process.env.TAX, + process.env.SWAP_THRESHOLD, + treasury.address ); + await agentFactory.setDefaultDelegatee(deployer.address); + + return { virtualToken, agentFactory, agentNft }; + } - const filter = personaFactory.filters.NewApplication; - const events = await personaFactory.queryFilter(filter, -1); + async function deployWithApplication() { + const base = await deployBaseContracts(); + const { agentFactory, virtualToken } = base; + const { founder } = await getAccounts(); + + // Prepare tokens for proposal + await virtualToken.mint(founder.address, PROPOSAL_THRESHOLD); + await virtualToken + .connect(founder) + .approve(agentFactory.target, PROPOSAL_THRESHOLD); + + const tx = await agentFactory + .connect(founder) + .proposeAgent( + genesisInput.name, + genesisInput.symbol, + genesisInput.tokenURI, + genesisInput.cores, + genesisInput.tbaSalt, + genesisInput.tbaImplementation, + genesisInput.daoVotingPeriod, + genesisInput.daoThreshold + ); + + const filter = agentFactory.filters.NewApplication; + const events = await agentFactory.queryFilter(filter, -1); const event = events[0]; const { id } = event.args; + return { applicationId: id, ...base }; + } - // Create proposal - await veToken.oracleTransfer( - [ethers.ZeroAddress], - [deployer.address], - [parseEther("100000000")] - ); - await veToken.delegate(deployer.address); - - await protocolDAO.propose( - [personaFactory.target], - [0], - [getExecuteCallData(personaFactory, id)], - "Create Jessica" - ); - - const daoFilter = protocolDAO.filters.ProposalCreated; - const daoEvents = await protocolDAO.queryFilter(daoFilter, -1); - const daoEvent = daoEvents[0]; - const daoProposalId = daoEvent.args[0]; + async function deployWithAgent() { + const base = await deployWithApplication(); + const { agentFactory, applicationId } = base; - await protocolDAO.castVote(daoProposalId, 1); - await mine(PROTOCOL_DAO_VOTING_PERIOD); + const { founder } = await getAccounts(); + await agentFactory + .connect(founder) + .executeApplication(applicationId, false); - await protocolDAO.execute(daoProposalId); - const factoryFilter = personaFactory.filters.NewPersona; - const factoryEvents = await personaFactory.queryFilter(factoryFilter, -1); + const factoryFilter = agentFactory.filters.NewPersona; + const factoryEvents = await agentFactory.queryFilter(factoryFilter, -1); const factoryEvent = factoryEvents[0]; - const { virtualId, token, dao } = factoryEvent.args; - const persona = { virtualId, token, dao }; - - const personaTokenContract = await ethers.getContractAt( - "AgentToken", - persona.token - ); - return { personaTokenContract }; + const { virtualId, token, veToken, dao, tba, lp } = await factoryEvent.args; + + return { + ...base, + agent: { + virtualId, + token, + veToken, + dao, + tba, + lp, + }, + }; } before(async function () { @@ -168,46 +187,48 @@ describe("Delegation", function () { }); it("should be able to retrieve past delegates", async function () { - const { personaTokenContract } = await loadFixture(deployBaseContracts); + const { agent } = await loadFixture(deployWithAgent); + const veToken = await ethers.getContractAt("AgentVeToken", agent.veToken); const [account1, account2, account3] = this.accounts; - await personaTokenContract.delegate(account1); + await veToken.delegate(account1); mine(1); const block1 = await ethers.provider.getBlockNumber(); - expect(await personaTokenContract.delegates(account1)).to.equal(account1); + expect(await veToken.delegates(account1)).to.equal(account1); - await personaTokenContract.delegate(account2); + await veToken.delegate(account2); mine(1); const block2 = await ethers.provider.getBlockNumber(); - await personaTokenContract.delegate(account3); + await veToken.delegate(account3); mine(1); const block3 = await ethers.provider.getBlockNumber(); expect( - await personaTokenContract.getPastDelegates(account1, block2) + await veToken.getPastDelegates(account1, block2) ).to.equal(account2); expect( - await personaTokenContract.getPastDelegates(account1, block3) + await veToken.getPastDelegates(account1, block3) ).to.equal(account3); expect( - await personaTokenContract.getPastDelegates(account1, block1) + await veToken.getPastDelegates(account1, block1) ).to.equal(account1); - expect(await personaTokenContract.delegates(account1)).to.equal(account3); + expect(await veToken.delegates(account1)).to.equal(account3); }); it("should be able to retrieve past delegates when there are more than 5 checkpoints", async function () { - const { personaTokenContract } = await loadFixture(deployBaseContracts); + const { agent } = await loadFixture(deployWithAgent); + const veToken = await ethers.getContractAt("AgentVeToken", agent.veToken); const blockNumber = await ethers.provider.getBlockNumber(); const [account1, account2, account3] = this.accounts; for (let i = 0; i < 8; i++) { - await personaTokenContract.delegate(this.accounts[i]); + await veToken.delegate(this.accounts[i]); } await mine(1); for (let i = 0; i < 8; i++) { expect( - await personaTokenContract.getPastDelegates( + await veToken.getPastDelegates( account1, blockNumber + i + 1 ) From 1de7c861548b70a3ba4b8616fdcf652e7185829b Mon Sep 17 00:00:00 2001 From: kw Date: Tue, 18 Jun 2024 13:55:45 +0800 Subject: [PATCH 4/4] agentDAO proposals will be auto executed --- test/contribution.js | 4 ---- test/rewardsV2.js | 1 - 2 files changed, 5 deletions(-) diff --git a/test/contribution.js b/test/contribution.js index b844d3a..be55a00 100644 --- a/test/contribution.js +++ b/test/contribution.js @@ -306,7 +306,6 @@ describe("Contribution", function () { await mine(600); - await agentDAO.execute(proposalId); if (isModel) { await minter.mint(proposalId); } @@ -546,7 +545,6 @@ describe("Contribution", function () { await mine(1); await agentDAO.connect(founder).castVote(proposalId, 1); await mine(1); - await agentDAO.execute(proposalId); // No agent token minted for dataset contribution const c1 = await createContribution( @@ -693,8 +691,6 @@ describe("Contribution", function () { await mine(1); await agentDAO.connect(founder).castVote(proposalId, 1); - await mine(1); - await agentDAO.execute(proposalId); // No agent token minted for dataset contribution const c1 = await createContribution( diff --git a/test/rewardsV2.js b/test/rewardsV2.js index 7914328..3f13ca8 100644 --- a/test/rewardsV2.js +++ b/test/rewardsV2.js @@ -298,7 +298,6 @@ describe("RewardsV2", function () { await mine(600); - await agentDAO.execute(proposalId); await minter.mint(proposalId); return proposalId;