From 947f6ea38ac4094ff95e3c00308836449217f21f Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 23 Oct 2025 11:53:24 +0300 Subject: [PATCH 01/13] add: improve ubi calculations --- contracts/ubi/UBISchemeV2.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/ubi/UBISchemeV2.sol b/contracts/ubi/UBISchemeV2.sol index ab6e8d98..c6faaeec 100644 --- a/contracts/ubi/UBISchemeV2.sol +++ b/contracts/ubi/UBISchemeV2.sol @@ -269,7 +269,7 @@ contract UBISchemeV2 is DAOUpgradeableContract { dailyCyclePool / max((prevDayClaimers * reserveFactor) / 10000, minActiveUsers); //update minActiveUsers as claimers grow - minActiveUsers = max(prevDayClaimers / 2, minActiveUsers); + minActiveUsers = (prevDayClaimers + minActiveUsers * 4) / 5; //smooth it a bit emit UBICalculated(currentDay, dailyUbi, block.number); } @@ -347,7 +347,8 @@ contract UBISchemeV2 is DAOUpgradeableContract { bool shouldStartEarlyCycle = currentDayInCycle() + 1 >= currentCycleLength || nextDailyPool > (dailyCyclePool * 105) / 100 || - currentBalance < (dailyCyclePool * (cycleLength - currentDayInCycle())); + currentBalance < + (dailyCyclePool * (cycleLength - currentDayInCycle() - 1)); uint256 _dailyCyclePool = dailyCyclePool; uint256 _dailyUbi; From cdbe90807dcaffb74403a909fea59429f962813d Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 23 Oct 2025 11:54:00 +0300 Subject: [PATCH 02/13] add: more verbose error --- contracts/reserve/GenericDistributionHelper.sol | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/reserve/GenericDistributionHelper.sol b/contracts/reserve/GenericDistributionHelper.sol index 7205f791..38be2267 100644 --- a/contracts/reserve/GenericDistributionHelper.sol +++ b/contracts/reserve/GenericDistributionHelper.sol @@ -67,7 +67,11 @@ contract GenericDistributionHelper is ); event RecipientUpdated(DistributionRecipient recipient, uint256 index); event RecipientAdded(DistributionRecipient recipient, uint256 index); - event BuyNativeFailed(string reason); + event BuyNativeFailed( + string reason, + uint256 amountToSell, + uint256 amountOutMinimum + ); receive() external payable {} @@ -148,9 +152,9 @@ contract GenericDistributionHelper is try IWETH(gasToken).withdraw(boughtNative) { // success } catch Error(string memory reason) { - emit BuyNativeFailed(reason); + emit BuyNativeFailed(reason, boughtNative, 0); } catch { - emit BuyNativeFailed("WETH withdraw failed"); + emit BuyNativeFailed("WETH withdraw failed", boughtNative, 0); } } @@ -308,7 +312,7 @@ contract GenericDistributionHelper is try ROUTER.exactInput(params) returns (uint256 amountOut) { return amountOut; } catch Error(string memory reason) { - emit BuyNativeFailed(reason); + emit BuyNativeFailed(reason, amountToSell, amountOutMinimum); return 0; } } From 4b153b4fdfc4c2225b6c8093f5b2f7446564735a Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 23 Oct 2025 12:05:39 +0300 Subject: [PATCH 03/13] add: disthelper deployer with recipients --- .prettierrc | 8 +-- hardhat.config.ts | 4 +- .../multichain-deploy/8_disthelper-deploy.ts | 63 +++++++++++-------- test/ubi/UBISchemeV2.test.ts | 5 +- 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/.prettierrc b/.prettierrc index dc3aa68e..b4f0966a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -10,12 +10,6 @@ "bracketSpacing": true, "explicitTypes": "always" } - }, - { - "files": "test/**/*.ts", - "options": { - "printWidth": 80 - } } ], "printWidth": 120, @@ -24,4 +18,4 @@ "explicitTypes": "always", "trailingComma": "none", "arrowParens": "avoid" -} \ No newline at end of file +} diff --git a/hardhat.config.ts b/hardhat.config.ts index ac85cc54..c6753ed1 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -286,8 +286,8 @@ const hhconfig: HardhatUserConfig = { accounts: [deployerPrivateKey] }, "development-xdc": { - ...xdc, - accounts: [deployerPrivateKey] + ...xdc + // url: "http://localhost:8545" } }, mocha: { diff --git a/scripts/multichain-deploy/8_disthelper-deploy.ts b/scripts/multichain-deploy/8_disthelper-deploy.ts index 1e9d1aff..c749ede0 100644 --- a/scripts/multichain-deploy/8_disthelper-deploy.ts +++ b/scripts/multichain-deploy/8_disthelper-deploy.ts @@ -41,6 +41,8 @@ export const deployHelpers = async () => { let [root, ...signers] = await ethers.getSigners(); const isProduction = network.name.includes("production"); + let networkEnv = network.name.split("-")[0]; + const celoNetwork = networkEnv + "-celo"; if (isProduction) verifyProductionSigner(root); //generic call permissions @@ -80,35 +82,44 @@ export const deployHelpers = async () => { await releaser(torelease, network.name, "deployment", false); console.log("setting nameservice addresses via guardian"); - const proposalContracts = [ - release.NameService, //nameservice - release.DistributionHelper - ]; - - const proposalEthValues = proposalContracts.map(_ => 0); - - const proposalFunctionSignatures = [ - "setAddresses(bytes32[],address[])", //add ubischeme - "addOrUpdateRecipient((uint32,uint32,address,uint8))" + const proposalActions = [ + [ + release.NameService, //nameservice + "setAddresses(bytes32[],address[])", //add ubischeme + ethers.utils.defaultAbiCoder.encode( + ["bytes32[]", "address[]"], + [[keccak256(toUtf8Bytes("DISTRIBUTION_HELPER"))], [DistHelper.address]] + ), + 0 + ], + [ + DistHelper.address, + "addOrUpdateRecipient((uint32,uint32,address,uint8))", + ethers.utils.defaultAbiCoder.encode( + ["uint32", "uint32", "address", "uint8"], + [dao[celoNetwork].CommunitySafe ? 9000 : 10000, network.config.chainId, release.UBIScheme, 1] //90% to ubi scheme + ), + 0 + ] ]; - const recipient = { - bps: 10000, // 100% (bps = basis points) - chainId: network.config.chainId, // XDC mainnet - addr: release.UBIScheme, - transferType: 1 // enum TransferType.Transfer = 0 - }; - const proposalFunctionInputs = [ - ethers.utils.defaultAbiCoder.encode( - ["bytes32[]", "address[]"], - [[keccak256(toUtf8Bytes("DISTRIBUTION_HELPER"))], [DistHelper.address]] - ), - ethers.utils.defaultAbiCoder.encode( - ["tuple(uint32 bps,uint32 chainId,address addr,uint8 transferType)"], - [[recipient.bps, recipient.chainId, recipient.addr, recipient.transferType]] - ) + if (dao[celoNetwork].CommunitySafe) { + proposalActions.push([ + DistHelper.address, + "addOrUpdateRecipient((uint32,uint32,address,uint8))", + ethers.utils.defaultAbiCoder.encode( + ["uint32", "uint32", "address", "uint8"], + [1000, 42220, dao[celoNetwork].CommunitySafe, 0] //10% to celo community safe + ), + 0 + ]); + } + const [proposalContracts, proposalFunctionSignatures, proposalFunctionInputs, proposalEthValues] = [ + proposalActions.map(_ => _[0]), + proposalActions.map(_ => _[1]), + proposalActions.map(_ => _[2]), + proposalActions.map(_ => _[3]) ]; - try { if (viaGuardians) { await executeViaSafe( diff --git a/test/ubi/UBISchemeV2.test.ts b/test/ubi/UBISchemeV2.test.ts index 4f9998a2..77a73aaa 100644 --- a/test/ubi/UBISchemeV2.test.ts +++ b/test/ubi/UBISchemeV2.test.ts @@ -84,7 +84,10 @@ describe("UBISchemeV2", () => { it("should deploy the ubi", async () => { const block = await ethers.provider.getBlock("latest"); const startUBI = block.timestamp; - ubi = await upgrades.deployProxy(await ethers.getContractFactory("UBISchemeV2"), [nameService.address, 1000]); + ubi = (await upgrades.deployProxy(await ethers.getContractFactory("UBISchemeV2"), [ + nameService.address, + 1000 + ])) as UBISchemeV2; const periodStart = await ubi.periodStart(); // initializing the ubi let encodedCall = ubi.interface.encodeFunctionData("setCycleLength", [1]); From 88b88a15cf62f3bc58cbfbbf3a8010c58fada3e7 Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 23 Oct 2025 12:23:06 +0300 Subject: [PATCH 04/13] fix: cross chain community treasury --- scripts/multichain-deploy/8_disthelper-deploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/multichain-deploy/8_disthelper-deploy.ts b/scripts/multichain-deploy/8_disthelper-deploy.ts index c749ede0..163656ed 100644 --- a/scripts/multichain-deploy/8_disthelper-deploy.ts +++ b/scripts/multichain-deploy/8_disthelper-deploy.ts @@ -109,7 +109,7 @@ export const deployHelpers = async () => { "addOrUpdateRecipient((uint32,uint32,address,uint8))", ethers.utils.defaultAbiCoder.encode( ["uint32", "uint32", "address", "uint8"], - [1000, 42220, dao[celoNetwork].CommunitySafe, 0] //10% to celo community safe + [1000, 42220, dao[celoNetwork].CommunitySafe, network.name === celoNetwork ? 1 : 0] //10% to celo community safe, use LZ bridge if not on celo ), 0 ]); From 929eef7676ab86cb9d99537ec51fd21037c06f7d Mon Sep 17 00:00:00 2001 From: sirpy Date: Sun, 26 Oct 2025 11:45:56 +0200 Subject: [PATCH 05/13] add: improve ubi smoothing --- contracts/ubi/UBISchemeV2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/ubi/UBISchemeV2.sol b/contracts/ubi/UBISchemeV2.sol index c6faaeec..b9279fd6 100644 --- a/contracts/ubi/UBISchemeV2.sol +++ b/contracts/ubi/UBISchemeV2.sol @@ -269,7 +269,7 @@ contract UBISchemeV2 is DAOUpgradeableContract { dailyCyclePool / max((prevDayClaimers * reserveFactor) / 10000, minActiveUsers); //update minActiveUsers as claimers grow - minActiveUsers = (prevDayClaimers + minActiveUsers * 4) / 5; //smooth it a bit + minActiveUsers = (prevDayClaimers + minActiveUsers * 29) / 30; //smooth it a bit emit UBICalculated(currentDay, dailyUbi, block.number); } From 65edbfcf5d3fa2b4f8eeb987e3472836522baf83 Mon Sep 17 00:00:00 2001 From: sirpy Date: Sun, 26 Oct 2025 14:40:29 +0200 Subject: [PATCH 06/13] add: verified erc1967proxy --- contracts/utils/ProxyFactory1967.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/utils/ProxyFactory1967.sol b/contracts/utils/ProxyFactory1967.sol index cb729734..2641d0f4 100644 --- a/contracts/utils/ProxyFactory1967.sol +++ b/contracts/utils/ProxyFactory1967.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT +//pragma solidity =0.8.16; // xdc deployed version pragma solidity >=0.8; import "@openzeppelin/contracts/proxy/Proxy.sol"; From 9bfe0860b1c3d69745da32a0d2b36b1aaf753792 Mon Sep 17 00:00:00 2001 From: sirpy Date: Mon, 27 Oct 2025 12:53:36 +0200 Subject: [PATCH 07/13] add: xdc gip-25 step1 ready --- package.json | 2 +- releases/deployment.json | 17 + .../0_proxyFactory-deploy.ts | 7 +- .../multichain-deploy/1_basicdao-deploy.ts | 27 +- scripts/multichain-deploy/helpers.ts | 35 +- scripts/proposals/gip-25-xdc-upgrade-ubi.ts | 444 ++++++++++++++++++ 6 files changed, 502 insertions(+), 30 deletions(-) create mode 100644 scripts/proposals/gip-25-xdc-upgrade-ubi.ts diff --git a/package.json b/package.json index db396e31..be7fb43b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gooddollar/goodprotocol", - "version": "2.0.34-beta.2", + "version": "2.0.34-beta.4", "description": "GoodDollar Protocol", "engines": { "node": ">=16.x" diff --git a/releases/deployment.json b/releases/deployment.json index ab5e348f..83bcc558 100644 --- a/releases/deployment.json +++ b/releases/deployment.json @@ -667,5 +667,22 @@ "MentoExchangeProvider": "0x1fad5a713da1f51e2a1ac4ca2e1d621919f0aba0", "MentoExpansionController": "0xe69bd25ca06264780e377e8d81a59271299adb36", "MentoProxyAdmin": "0x54f44fBE2943c2196D94831288E716cdeAF5657" + }, + "production-xdc": { + "ProxyFactory": "0x5BE34022c26FA03a8d6926314a42414c7ca2dF51", + "GuardiansSafe": "0xE0c5daa7CC6F88d29505f702a53bb5E67600e7Ec", + "CUSD": "0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1", + "ReserveToken": "0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1", + "Identity": "0x27a4a02C9ed591E1a86e2e5D05870292c34622C9", + "DAOCreator": "0xa2B9993D198904e4bdCE48379FDff65405607F42", + "FeeFormula": "0xf4e9Af892A09F04B7408266777198206E7f80D46", + "NameService": "0x1e5154Bf5e31FF56051bbd45958b879Fb7a290FE", + "Avatar": "0x21eaC3fE218307BeE0463F77EBcA3b50F452C0Ce", + "GoodDollar": "0xEC2136843a983885AebF2feB3931F73A8eBEe50c", + "Controller": "0x75a8bE0C2dEaDEd8Fc9ECEB5F01ad0B979b7AD03", + "AdminWallet": "0x66fc1bE551f752706130b6f54d84141F8c2Ae8Bb", + "Faucet": "0x7344Da1Be296f03fbb8082aDaC5696058B5a9bd9", + "Invites": "0x6bd698566632bf2e81e2278f1656CB24aAF06D2e", + "UBIScheme": "0x22867567E2D80f2049200E25C6F31CB6Ec2F0faf" } } diff --git a/scripts/multichain-deploy/0_proxyFactory-deploy.ts b/scripts/multichain-deploy/0_proxyFactory-deploy.ts index 85685709..7beb5933 100644 --- a/scripts/multichain-deploy/0_proxyFactory-deploy.ts +++ b/scripts/multichain-deploy/0_proxyFactory-deploy.ts @@ -32,7 +32,8 @@ export const deployUniversalProxyFactory = async () => { if (name.includes("staging")) { deployTx.gasLimit = 892000; } else if (name.includes("production")) { - deployTx.gasLimit = 890000; + // deployTx.gasLimit = 890000; // was used on celo + deployTx.gasLimit = 1500000; //xdc needs more gas } const rawTx = ethers.utils.serializeTransaction(deployTx); @@ -121,8 +122,8 @@ const deployProxyMethod2 = async (defaultAdmin = null) => { await verifyContract(deployed.address, artifact.sourceName + ":" + artifact.contractName); }; export const main = async (networkName = name) => { - // await deployProxy(); - await deployProxyMethod2(); + await deployProxy(); + // await deployProxyMethod2(); }; if (process.argv[1].includes("proxyFactory-deploy")) { diff --git a/scripts/multichain-deploy/1_basicdao-deploy.ts b/scripts/multichain-deploy/1_basicdao-deploy.ts index 3f355074..5d30ec1d 100644 --- a/scripts/multichain-deploy/1_basicdao-deploy.ts +++ b/scripts/multichain-deploy/1_basicdao-deploy.ts @@ -24,6 +24,7 @@ import ProtocolSettings from "../../releases/deploy-settings.json"; import dao from "../../releases/deployment.json"; import { TransactionResponse } from "@ethersproject/providers"; import { getImplementationAddress } from "@openzeppelin/upgrades-core"; +import { IdentityV3 } from "../../types"; const printDeploy = async (c: Contract | TransactionResponse): Promise => { if (c instanceof Contract) { await c.deployed(); @@ -60,8 +61,8 @@ export const createDAO = async () => { const FeeFormulaFactory = new ethers.ContractFactory(FeeFormulaABI.abi, FeeFormulaABI.bytecode, root); console.log("deploying identity"); - let Identity; - if (release.Identity) Identity = await ethers.getContractAt("IdentityV3", release.Identity); + let Identity: IdentityV3; + if (release.Identity) Identity = (await ethers.getContractAt("IdentityV3", release.Identity)) as IdentityV3; else Identity = (await deployDeterministic( { @@ -70,7 +71,7 @@ export const createDAO = async () => { isUpgradeable: true }, [root.address, ethers.constants.AddressZero] - ).then(printDeploy)) as Contract; + ).then(printDeploy)) as IdentityV3; let daoCreator; if (release.DAOCreator) daoCreator = await DAOCreatorFactory.attach(release.DAOCreator); @@ -131,10 +132,7 @@ export const createDAO = async () => { ).then(printDeploy)) as Contract; } - // console.log("setting identity auth period"); - // await Identity.setAuthenticationPeriod(365).then(printDeploy); - - const avatar = await daoCreator.avatar(); + const avatar = release.Avatar || (await daoCreator.avatar()); let Avatar = new ethers.Contract( avatar, ["function owner() view returns (address)", "function nativeToken() view returns (address)"], @@ -151,7 +149,7 @@ export const createDAO = async () => { ["function owner() view returns (address)", "function nativeToken() view returns (address)"], root ); - let schemes = [daoOwner, Identity.address]; + let schemes = [daoOwner, release.GuardiansSafe]; console.log("setting schemes", schemes); @@ -160,7 +158,7 @@ export const createDAO = async () => { Avatar.address, schemes, schemes.map(_ => ethers.constants.HashZero), - ["0x0000001f", "0x00000001"], + ["0x0000001f", "0x0000001f"], "" ) .then(printDeploy); @@ -172,6 +170,7 @@ export const createDAO = async () => { const controller = await Avatar.owner(); + console.log("deploying nameservice"); const NameService = await deployDeterministic({ name: "NameService", isUpgradeable: true }, [ controller, ["CONTROLLER", "AVATAR", "IDENTITY", "GOODDOLLAR"].map(_ => ethers.utils.keccak256(ethers.utils.toUtf8Bytes(_))), @@ -189,6 +188,16 @@ export const createDAO = async () => { if (isProduction) { console.log("renouncing minting rights on production env"); await GoodDollar.renounceMinter().then(printDeploy); + const ctrl = await ethers.getContractAt("Controller", await Avatar.owner()); + console.log("setting identity authentication period to 180 days"); + await ctrl + .genericCall( + Identity.address, + Identity.interface.encodeFunctionData("setAuthenticationPeriod", [180]), + Avatar.address, + 0 + ) + .then(printDeploy); } const daoOwnerDaoPermissions = await Controller.getSchemePermissions(daoOwner, Avatar.address); diff --git a/scripts/multichain-deploy/helpers.ts b/scripts/multichain-deploy/helpers.ts index 3398eeac..ab54b911 100644 --- a/scripts/multichain-deploy/helpers.ts +++ b/scripts/multichain-deploy/helpers.ts @@ -67,6 +67,7 @@ export const deploySuperGoodDollar = async ( [superfluidHost] ).then(printDeploy)) as Contract; + console.log("supergoodollar logic deployed:", SuperGoodDollar.address); const uupsFactory = await ethers.getContractFactory("UUPSProxy"); const GoodDollarProxy = (await deployDeterministic( { @@ -75,9 +76,13 @@ export const deploySuperGoodDollar = async ( }, [] ).then(printDeploy)) as Contract; - - await GoodDollarProxy.initializeProxy(SuperGoodDollar.address).then(printDeploy); - + console.log("supergoodollar proxy deployed:", GoodDollarProxy.address); + try { + await GoodDollarProxy.initializeProxy(SuperGoodDollar.address).then(printDeploy); + } catch (e) { + console.log("failed initialized supergd proxy. probably already was init"); + } + console.log("supergoodollar proxy initialized:", GoodDollarProxy.address); let OutFlowNFT = await ethers.getContractAt("ConstantOutflowNFT", ethers.constants.AddressZero); let InFlowNFT = await ethers.getContractAt("ConstantOutflowNFT", ethers.constants.AddressZero); @@ -103,9 +108,13 @@ export const deploySuperGoodDollar = async ( await InFlowNFT.initializeProxy(superfluidInflowNFTLogic); } console.log("initializing supergooddollar"); - await SuperGoodDollar.attach(GoodDollarProxy.address)[ - "initialize(string,string,uint256,address,address,address,address)" - ](...tokenArgs); + try { + await SuperGoodDollar.attach(GoodDollarProxy.address)[ + "initialize(string,string,uint256,address,address,address,address)" + ](...tokenArgs); + } catch (e) { + console.log("failed initializing supergd. probably already was init"); + } const GoodDollar = await ethers.getContractAt("ISuperGoodDollar", GoodDollarProxy.address); @@ -246,7 +255,7 @@ export const upgradeDeterministicToNewImplViaGuardian = async (contract, factory if (implAddr && implAddr !== "0x") { console.log("proxy exists and impl set already:", contract.name, proxyAddr, implAddr); console.log("Deploying:", contract.name, " implementation", { - proxyAddr, + proxyAddr }); const tx = await Contract.deploy(GAS_SETTINGS); @@ -254,9 +263,7 @@ export const upgradeDeterministicToNewImplViaGuardian = async (contract, factory console.log("implementation deployed:", contract.name, impl.address); await countTotalGas(tx, contract.name); - const proposalContracts = [ - release.DistributionHelper - ]; + const proposalContracts = [release.DistributionHelper]; const proposalEthValues = proposalContracts.map(_ => 0); @@ -264,12 +271,7 @@ export const upgradeDeterministicToNewImplViaGuardian = async (contract, factory "upgradeTo(address)" //add ubischeme ]; - const proposalFunctionInputs = [ - ethers.utils.defaultAbiCoder.encode( - ["address"], - [impl.address] - ) - ]; + const proposalFunctionInputs = [ethers.utils.defaultAbiCoder.encode(["address"], [impl.address])]; let [root] = await ethers.getSigners(); await executeViaGuardian( proposalContracts, @@ -292,7 +294,6 @@ export const upgradeDeterministicToNewImplViaGuardian = async (contract, factory } }; - export const executeViaGuardian = async ( contracts, ethValues, diff --git a/scripts/proposals/gip-25-xdc-upgrade-ubi.ts b/scripts/proposals/gip-25-xdc-upgrade-ubi.ts new file mode 100644 index 00000000..978d00cc --- /dev/null +++ b/scripts/proposals/gip-25-xdc-upgrade-ubi.ts @@ -0,0 +1,444 @@ +// Part 1 UBI+Bridge +// deploy dao to xdc - Done +// deploy bridge to xdc - Done +// give minting rights to xdc bridge - done +// upgrade to improved identity + ubischeme on celo + fuse +// upgrade bridge on celo/ethereum/fuse - make sure they include xdc +// burn celo bridge locked supply (now each chain has its own supply and all bridges are mint/burn) +// add celo bridge as minter + +// Part 2 Reserve +// create uniswap pools on xdc +// calculate how much G$s each reserve is backing +// deploy mento reserve to xdc with calculated parameters +// give mento broker minting rights on xdc +// deploy distribution helper +// transfer usdc to xdc reserve +// update celo reserve parameters accordingly + +import { network, ethers, upgrades } from "hardhat"; +import { reset } from "@nomicfoundation/hardhat-network-helpers"; +import { defaultsDeep, last } from "lodash"; +import prompt from "prompt"; + +import { executeViaGuardian, executeViaSafe, verifyProductionSigner } from "../multichain-deploy/helpers"; + +import ProtocolSettings from "../../releases/deploy-settings.json"; + +import dao from "../../releases/deployment.json"; +import { + CeloDistributionHelper, + Controller, + FuseOldBridgeKill, + GoodMarketMaker, + IBancorExchangeProvider, + IBroker, + IdentityV3, + IGoodDollar, + IGoodDollarExchangeProvider, + IGoodDollarExpansionController, + IMentoReserve, + IMessagePassingBridge, + ProtocolUpgradeV4Mento, + UBISchemeV2 +} from "../../types"; +import releaser from "../releaser"; +let { name: networkName } = network; +const isSimulation = network.name === "hardhat" || network.name === "fork" || network.name === "localhost"; + +const bridgeUpgradeImpl = { + "production-celo": "0x7bDaF2Fb332761b2a6A565a43ccB0ACfC36d2C3D", + production: "0x6f252280eB53df085eAD27BBe55d615741A8268D", + "production-mainnet": "0x6f252280eB53df085eAD27BBe55d615741A8268D" +}; +export const upgradeCeloStep1 = async (network, checksOnly) => { + let [root] = await ethers.getSigners(); + + const isProduction = networkName.includes("production"); + + if (isProduction) verifyProductionSigner(root); + + let networkEnv = networkName; + let guardian = root; + if (isSimulation) { + networkEnv = network; + } + + let release: { [key: string]: any } = dao[networkEnv]; + + console.log("signer:", root.address, { networkEnv, isSimulation, isProduction, release }); + + if (isSimulation) { + networkEnv = network; + guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe); + + await root.sendTransaction({ + value: ethers.utils.parseEther("1"), + to: release.GuardiansSafe + }); + } + + const ubiImpl = await ethers.deployContract("UBISchemeV2"); + const identityImpl = await ethers.deployContract("IdentityV3"); + const bridgeImpl = bridgeUpgradeImpl[networkEnv]; + const upgradeCall = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("upgrade()")).substring(0, 10); + + // Extract the first four bytes as the function selector + console.log("deployed new impls", { ubiImpl: ubiImpl.address, identityImpl: identityImpl.address }); + + const gd = (await ethers.getContractAt("IGoodDollar", release.GoodDollar)) as IGoodDollar; + const avatarBalance = await gd.balanceOf(release.Avatar); + const bridgeBalance = await gd.balanceOf(release.MpbBridge); + const toBurn = avatarBalance.add(bridgeBalance); + + console.log("calculated burn amount:", { + toBurn: toBurn.toString(), + avatarBalance: avatarBalance.toString(), + bridgeBalance: bridgeBalance.toString() + }); + const proposalActions = [ + [release.UBIScheme, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [ubiImpl.address]), "0"], //upgrade ubi + [ + release.Identity, + "upgradeTo(address)", + ethers.utils.defaultAbiCoder.encode(["address"], [identityImpl.address]), + "0" + ], //upgrade identity + [ + release.MpbBridge, + "upgradeToAndCall(address,bytes)", + ethers.utils.defaultAbiCoder.encode(["address", "bytes"], [bridgeImpl, upgradeCall]), + "0" + ], //upgrade bridge + [ + release.MpbBridge, + "withdraw(address,uint256)", + ethers.utils.defaultAbiCoder.encode(["address", "uint256"], [release.GoodDollar, 0]), + "0" + ], //withdraw locked gd + [release.GoodDollar, "burn(uint256)", ethers.utils.defaultAbiCoder.encode(["uint256"], [toBurn.toString()]), "0"], //burn locked supply on celo bridge + [ + release.Controller, + "registerScheme(address,bytes32,bytes4,address)", + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32", "bytes4", "address"], + [release.MpbBridge, ethers.constants.HashZero, "0x00000001", release.Avatar] + ), + "0" + ] + ]; + + console.log({ + networkEnv, + guardian: guardian.address, + isSimulation, + isProduction, + release + }); + + const proposalContracts = proposalActions.map(a => a[0]); + const proposalFunctionSignatures = proposalActions.map(a => a[1]); + const proposalFunctionInputs = proposalActions.map(a => a[2]); + const proposalEthValues = proposalActions.map(a => a[3]); + if (isProduction && !checksOnly) { + await executeViaSafe( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + release.GuardiansSafe, + "celo" + ); + } else if (!checksOnly) { + await executeViaGuardian( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + guardian, + networkEnv + ); + } + + if (isSimulation || !isProduction) { + const supplyAfter = await (await ethers.getContractAt("IGoodDollar", release.GoodDollar)).totalSupply(); + const bridgeBalanceAfter = await ( + await ethers.getContractAt("IGoodDollar", release.GoodDollar) + ).balanceOf(release.MpbBridge); + console.log("Bridge balance after upgrade:", { bridgeBalanceAfter }); + console.log("Supply after upgrade:", { supplyAfter }); + + const ctrl = (await ethers.getContractAt("Controller", release.Controller)) as Controller; + const isMinterScheme = await ctrl.getSchemePermissions(release.MpbBridge, release.Avatar); + console.log("Bridge minter permissions on avatar:", isMinterScheme); + // check xdc chainid in bridge + const mpb = (await ethers.getContractAt("IMessagePassingBridge", release.MpbBridge)) as IMessagePassingBridge; + console.log("xdc lz chainid:", await mpb.toLzChainId(50)); + const ubi = (await ethers.getContractAt("UBISchemeV2", release.UBIScheme)) as UBISchemeV2; + const claimer = await ethers.getImpersonatedSigner("0xA48840D89a761502A4a7d995c74f3864D651A87F"); + const identity = (await ethers.getContractAt("IdentityV3", release.Identity)) as IdentityV3; + const tx = await (await identity.connect(claimer).connectAccount(guardian.address)).wait(); + console.log("Identity connect account tx:", tx); + const claimTx = await (await ubi.connect(guardian).claim()).wait(); + console.log("UBI claim from connected account tx:", claimTx.events); + } +}; + +export const upgradeFuseStep1 = async (network, checksOnly) => { + let [root] = await ethers.getSigners(); + + const isProduction = networkName.includes("production"); + + if (isProduction) verifyProductionSigner(root); + + let networkEnv = networkName; + let guardian = root; + if (isSimulation) { + networkEnv = network; + } + + let release: { [key: string]: any } = dao[networkEnv]; + + console.log("signer:", root.address, { networkEnv, isSimulation, isProduction, release }); + + if (isSimulation) { + networkEnv = network; + guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe); + + await root.sendTransaction({ + value: ethers.utils.parseEther("1"), + to: release.GuardiansSafe + }); + } + + const ubiImpl = await ethers.deployContract("UBISchemeV2"); + const identityImpl = await ethers.deployContract("IdentityV3"); + const bridgeImpl = bridgeUpgradeImpl[networkEnv]; + const upgradeCall = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("upgrade()")).substring(0, 10); + + // Extract the first four bytes as the function selector + console.log("deployed new impls", { ubiImpl: ubiImpl.address, identityImpl: identityImpl.address }); + + const proposalActions = [ + [release.UBIScheme, "upgradeTo(address)", ethers.utils.defaultAbiCoder.encode(["address"], [ubiImpl.address]), "0"], //upgrade ubi + [ + release.Identity, + "upgradeTo(address)", + ethers.utils.defaultAbiCoder.encode(["address"], [identityImpl.address]), + "0" + ], //upgrade identity + [ + release.MpbBridge, + "upgradeToAndCall(address,bytes)", + ethers.utils.defaultAbiCoder.encode(["address", "bytes"], [bridgeImpl, upgradeCall]), + "0" + ] //upgrade bridge + ]; + + console.log({ + networkEnv, + guardian: guardian.address, + isSimulation, + isProduction, + release + }); + + const proposalContracts = proposalActions.map(a => a[0]); + const proposalFunctionSignatures = proposalActions.map(a => a[1]); + const proposalFunctionInputs = proposalActions.map(a => a[2]); + const proposalEthValues = proposalActions.map(a => a[3]); + if (isProduction && !checksOnly) { + await executeViaSafe( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + release.GuardiansSafe, + "celo" + ); + } else if (!checksOnly) { + await executeViaGuardian( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + guardian, + networkEnv + ); + } + + if (isSimulation || !isProduction) { + const ctrl = (await ethers.getContractAt("Controller", release.Controller)) as Controller; + const isMinterScheme = await ctrl.getSchemePermissions(release.MpbBridge, release.Avatar); + console.log("Bridge minter permissions on avatar:", isMinterScheme); + // check xdc chainid in bridge + const mpb = (await ethers.getContractAt("IMessagePassingBridge", release.MpbBridge)) as IMessagePassingBridge; + console.log("xdc lz chainid:", await mpb.toLzChainId(50)); + const ubi = (await ethers.getContractAt("UBISchemeV2", release.UBIScheme)) as UBISchemeV2; + const claimer = await ethers.getImpersonatedSigner("0xA48840D89a761502A4a7d995c74f3864D651A87F"); + const identity = (await ethers.getContractAt("IdentityV3", release.Identity)) as IdentityV3; + const tx = await (await identity.connect(claimer).connectAccount(guardian.address)).wait(); + console.log("Identity connect account tx:", tx); + const claimTx = await (await ubi.connect(guardian).claim()).wait(); + console.log("UBI claim from connected account tx:", claimTx.events); + } +}; + +export const upgradeEthStep1 = async (network, checksOnly) => { + let [root] = await ethers.getSigners(); + + const isProduction = networkName.includes("production"); + + if (isProduction) verifyProductionSigner(root); + + let networkEnv = networkName; + let guardian = root; + if (isSimulation) { + networkEnv = network; + } + + let release: { [key: string]: any } = dao[networkEnv]; + + console.log("signer:", root.address, { networkEnv, isSimulation, isProduction, release }); + + if (isSimulation) { + networkEnv = network; + guardian = await ethers.getImpersonatedSigner(release.GuardiansSafe); + + await root.sendTransaction({ + value: ethers.utils.parseEther("1"), + to: release.GuardiansSafe + }); + } + + const bridgeImpl = bridgeUpgradeImpl[networkEnv]; + const upgradeCall = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("upgrade()")).substring(0, 10); + + const proposalActions = [ + [ + release.MpbBridge, + "upgradeToAndCall(address,bytes)", + ethers.utils.defaultAbiCoder.encode(["address", "bytes"], [bridgeImpl, upgradeCall]), + "0" + ] //upgrade bridge + ]; + + console.log({ + networkEnv, + guardian: guardian.address, + isSimulation, + isProduction, + release + }); + + const proposalContracts = proposalActions.map(a => a[0]); + const proposalFunctionSignatures = proposalActions.map(a => a[1]); + const proposalFunctionInputs = proposalActions.map(a => a[2]); + const proposalEthValues = proposalActions.map(a => a[3]); + if (isProduction && !checksOnly) { + await executeViaSafe( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + release.GuardiansSafe, + "celo" + ); + } else if (!checksOnly) { + await executeViaGuardian( + proposalContracts, + proposalEthValues, + proposalFunctionSignatures, + proposalFunctionInputs, + guardian, + networkEnv + ); + } + + if (isSimulation || !isProduction) { + const ctrl = (await ethers.getContractAt("Controller", release.Controller)) as Controller; + const isMinterScheme = await ctrl.getSchemePermissions(release.MpbBridge, release.Avatar); + console.log("Bridge minter permissions on avatar:", isMinterScheme); + // check xdc chainid in bridge + const mpb = (await ethers.getContractAt("IMessagePassingBridge", release.MpbBridge)) as IMessagePassingBridge; + console.log("xdc lz chainid:", await mpb.toLzChainId(50)); + } +}; + +const calculateReserveParams = async () => { + const celoProvider = new ethers.providers.JsonRpcProvider("https://forno.celo.org"); + const gd = await ethers.getContractAt("GoodDollar", dao["production-celo"].GoodDollar); + const celoCusd = (await ethers.getContractAt("IERC20", dao["production-celo"].CUSD)).connect(celoProvider); + const gdCelo = gd.connect(celoProvider); + const totalSupplyCelo = await gdCelo.totalSupply(); + const reserveBalance = await celoCusd.balanceOf(dao["production-celo"].MentoReserve); + const xdcReserveBalance = ethers.utils.parseUnits("200000", 18); //200k in xdc + const totalUSD = reserveBalance.add(xdcReserveBalance); //reserve + 200k in xdc + const xdcSupplyShare = xdcReserveBalance.mul(1e8).div(totalUSD); + const xdcGdSupplyEquivalent = totalSupplyCelo.mul(xdcSupplyShare).div(1e8); + const price = ethers.utils.parseUnits("0.00013", 8); + const celoGdSupplyEquivalent = totalSupplyCelo.sub(xdcGdSupplyEquivalent); + + console.log({ + totalSupplyCelo, + reserveBalance, + totalUSD, + xdcSupplyShare, + xdcGdSupplyEquivalent, + celoGdSupplyEquivalent + }); + + // uint32 reserveRatio = uint32( + // (cUSDBalance * 1e18 * 1e8) / (price * totalGlobalSupply) + // ); + //calculate reserve ratio + const reserveRatioXdc = xdcReserveBalance + .mul(ethers.BigNumber.from("100000000")) //1e8 + .mul(ethers.BigNumber.from("1000000000000000000")) //1e18 + .div(xdcGdSupplyEquivalent.mul(price)); + console.log( + "recommended reserve ratio for xdc:", + reserveRatioXdc.toString(), + reserveRatioXdc.div("10000000000").toNumber() / 1e8 + ); + + //calcualte reserve ratio for celo + const reserveRatioCelo = reserveBalance + .mul(ethers.BigNumber.from("100000000")) //1e8 + .mul(ethers.BigNumber.from("1000000000000000000")) //1e18 + .div(celoGdSupplyEquivalent.mul(price)); + + console.log( + "recommended reserve ratio for celo:", + reserveRatioCelo.toString(), + reserveRatioCelo.div("10000000000").toNumber() / 1e8 + ); +}; + +export const main = async () => { + // await calculateReserveParams(); + // return; + prompt.start(); + const { network } = await prompt.get(["network"]); + + console.log("running step:", { network }); + const chain = last(network.split("-")) || "fuse"; + console.log("detected chain:", chain, network); + switch (chain) { + case "mainnet": + await upgradeEthStep1(network, false); + + break; + case "production": + case "fuse": + await upgradeFuseStep1(network, false); + + break; + case "celo": + await upgradeCeloStep1(network, false); + + break; + } +}; + +main().catch(console.log); From 71c0ebaed9e0c4acdba8792cd90fc32f03f1843f Mon Sep 17 00:00:00 2001 From: sirpy Date: Mon, 27 Oct 2025 13:02:12 +0200 Subject: [PATCH 08/13] fix: correct expected bridge impl per chain address --- scripts/proposals/gip-25-xdc-upgrade-ubi.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/proposals/gip-25-xdc-upgrade-ubi.ts b/scripts/proposals/gip-25-xdc-upgrade-ubi.ts index 978d00cc..ad36b192 100644 --- a/scripts/proposals/gip-25-xdc-upgrade-ubi.ts +++ b/scripts/proposals/gip-25-xdc-upgrade-ubi.ts @@ -45,11 +45,10 @@ import { import releaser from "../releaser"; let { name: networkName } = network; const isSimulation = network.name === "hardhat" || network.name === "fork" || network.name === "localhost"; - const bridgeUpgradeImpl = { "production-celo": "0x7bDaF2Fb332761b2a6A565a43ccB0ACfC36d2C3D", production: "0x6f252280eB53df085eAD27BBe55d615741A8268D", - "production-mainnet": "0x6f252280eB53df085eAD27BBe55d615741A8268D" + "production-mainnet": "0x7baFe060A37E31E707b8B28a90a36731ee99aFBa" }; export const upgradeCeloStep1 = async (network, checksOnly) => { let [root] = await ethers.getSigners(); From 7d7ed7ab8acb8309cb6a8c2f1101de2a6a5268c3 Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 6 Nov 2025 12:20:31 +0200 Subject: [PATCH 09/13] fix: safe proposal and mpb settings --- scripts/multichain-deploy/helpers.ts | 12 +++-- scripts/proposals/gip-25-xdc-upgrade-ubi.ts | 58 +++++++++++---------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/scripts/multichain-deploy/helpers.ts b/scripts/multichain-deploy/helpers.ts index ab54b911..948144c1 100644 --- a/scripts/multichain-deploy/helpers.ts +++ b/scripts/multichain-deploy/helpers.ts @@ -368,7 +368,7 @@ export const executeViaSafe = async ( // new ethers.providers.JsonRpcProvider("https://rpc.flashbots.net") // ); let chainId = 1; - let provider = "https://mainnet.infura.io"; + let provider = "https://mainnet.gateway.tenderly.co"; if (typeof safeSignerOrNetwork === "string") { switch (safeSignerOrNetwork) { case "mainnet": @@ -396,18 +396,20 @@ export const executeViaSafe = async ( let txServiceUrl; switch (chainId) { case 1: - txServiceUrl = "https://safe-transaction-mainnet.safe.global"; + // txServiceUrl = "https://safe-transaction-mainnet.safe.global"; break; case 122: - txServiceUrl = "https://transaction-fuse.safe.fuse.io"; + txServiceUrl = "https://transaction-fuse.safe.fuse.io/api"; break; case 42220: - txServiceUrl = "https://safe-transaction-celo.safe.global"; + // txServiceUrl = "https://safe-transaction-celo.safe.global"; break; } console.log("creating safe adapter", { txServiceUrl }); const safeService = new SafeApiKit({ - chainId: BigInt(chainId) + chainId: BigInt(chainId), + txServiceUrl, + apiKey: process.env.SAFE_TX_SERVICE_API_KEY || "" }); const safeSdk = await Safe.init({ diff --git a/scripts/proposals/gip-25-xdc-upgrade-ubi.ts b/scripts/proposals/gip-25-xdc-upgrade-ubi.ts index ad36b192..96ed9031 100644 --- a/scripts/proposals/gip-25-xdc-upgrade-ubi.ts +++ b/scripts/proposals/gip-25-xdc-upgrade-ubi.ts @@ -21,28 +21,15 @@ import { reset } from "@nomicfoundation/hardhat-network-helpers"; import { defaultsDeep, last } from "lodash"; import prompt from "prompt"; -import { executeViaGuardian, executeViaSafe, verifyProductionSigner } from "../multichain-deploy/helpers"; - -import ProtocolSettings from "../../releases/deploy-settings.json"; +import { + executeViaGuardian, + executeViaSafe, + verifyContract, + verifyProductionSigner +} from "../multichain-deploy/helpers"; import dao from "../../releases/deployment.json"; -import { - CeloDistributionHelper, - Controller, - FuseOldBridgeKill, - GoodMarketMaker, - IBancorExchangeProvider, - IBroker, - IdentityV3, - IGoodDollar, - IGoodDollarExchangeProvider, - IGoodDollarExpansionController, - IMentoReserve, - IMessagePassingBridge, - ProtocolUpgradeV4Mento, - UBISchemeV2 -} from "../../types"; -import releaser from "../releaser"; +import { Controller, IdentityV3, IGoodDollar, IMessagePassingBridge, UBISchemeV2 } from "../../types"; let { name: networkName } = network; const isSimulation = network.name === "hardhat" || network.name === "fork" || network.name === "localhost"; const bridgeUpgradeImpl = { @@ -109,6 +96,15 @@ export const upgradeCeloStep1 = async (network, checksOnly) => { ethers.utils.defaultAbiCoder.encode(["address", "bytes"], [bridgeImpl, upgradeCall]), "0" ], //upgrade bridge + [ + release.MpbBridge, + "setConfig(uint16,uint16,uint256,bytes)", + ethers.utils.defaultAbiCoder.encode( + ["uint16", "uint16", "uint256", "bytes"], + [0, 365, 5, "0x000000000000000000000000000000000000000000000000000000000000000f"] + ), + "0" + ], //fix xdc bridge setting of outbound blocks confirmations [ release.MpbBridge, "withdraw(address,uint256)", @@ -160,10 +156,9 @@ export const upgradeCeloStep1 = async (network, checksOnly) => { } if (isSimulation || !isProduction) { - const supplyAfter = await (await ethers.getContractAt("IGoodDollar", release.GoodDollar)).totalSupply(); - const bridgeBalanceAfter = await ( - await ethers.getContractAt("IGoodDollar", release.GoodDollar) - ).balanceOf(release.MpbBridge); + const gd = await ethers.getContractAt("IGoodDollar", release.GoodDollar); + const supplyAfter = await gd.totalSupply(); + const bridgeBalanceAfter = await gd.balanceOf(release.MpbBridge); console.log("Bridge balance after upgrade:", { bridgeBalanceAfter }); console.log("Supply after upgrade:", { supplyAfter }); @@ -231,7 +226,16 @@ export const upgradeFuseStep1 = async (network, checksOnly) => { "upgradeToAndCall(address,bytes)", ethers.utils.defaultAbiCoder.encode(["address", "bytes"], [bridgeImpl, upgradeCall]), "0" - ] //upgrade bridge + ], //upgrade bridge + [ + release.MpbBridge, + "setConfig(uint16,uint16,uint256,bytes)", + ethers.utils.defaultAbiCoder.encode( + ["uint16", "uint16", "uint256", "bytes"], + [0, 365, 5, "0x000000000000000000000000000000000000000000000000000000000000000f"] + ), + "0" + ] //fix xdc bridge setting of outbound blocks confirmations ]; console.log({ @@ -253,7 +257,7 @@ export const upgradeFuseStep1 = async (network, checksOnly) => { proposalFunctionSignatures, proposalFunctionInputs, release.GuardiansSafe, - "celo" + "fuse" ); } else if (!checksOnly) { await executeViaGuardian( @@ -341,7 +345,7 @@ export const upgradeEthStep1 = async (network, checksOnly) => { proposalFunctionSignatures, proposalFunctionInputs, release.GuardiansSafe, - "celo" + "mainnet" ); } else if (!checksOnly) { await executeViaGuardian( From 5ac30569c6a8744fdfb6a501d47fb7baf02478b7 Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 6 Nov 2025 12:21:53 +0200 Subject: [PATCH 10/13] fix: ubi updates tests --- package.json | 5 +- test/governance/ClaimersDistribution.test.ts | 2 +- test/ubi/UBISchemeCycle.test.ts | 93 +++++---------- yarn.lock | 118 +++++++++++++++++-- 4 files changed, 143 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index be7fb43b..00199f13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gooddollar/goodprotocol", - "version": "2.0.34-beta.4", + "version": "2.1.0", "description": "GoodDollar Protocol", "engines": { "node": ">=16.x" @@ -66,6 +66,7 @@ "@babel/preset-env": "*", "@babel/register": "*", "@celo-tools/celo-ethers-wrapper": "^0.4.0", + "@chainlink/env-enc": "^1.0.5", "@gnosis.pm/safe-core-sdk": "^3.2.0", "@gnosis.pm/safe-core-sdk-types": "^1.7.0", "@gnosis.pm/safe-ethers-lib": "^1.7.0", @@ -82,7 +83,7 @@ "@openzeppelin/contracts": "^4.8.0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "@openzeppelin/hardhat-upgrades": "^1.22.1", - "@safe-global/api-kit": "^3.0.1", + "@safe-global/api-kit": "^4.0.0", "@safe-global/protocol-kit": "^6.0.2", "@safe-global/types-kit": "^2.0.1", "@superfluid-finance/ethereum-contracts": "1.8.1", diff --git a/test/governance/ClaimersDistribution.test.ts b/test/governance/ClaimersDistribution.test.ts index 7d11d538..e87c69a3 100644 --- a/test/governance/ClaimersDistribution.test.ts +++ b/test/governance/ClaimersDistribution.test.ts @@ -257,6 +257,6 @@ describe("ClaimersDistribution", () => { // console.log({ totalGas }, tx.gasUsed.toNumber(), tx2.gasUsed.toNumber()); } console.log(Object.keys(gasCosts)); - expect(totalGas / 30).lt(315000); + expect(totalGas / 30).lt(316000); }); }); diff --git a/test/ubi/UBISchemeCycle.test.ts b/test/ubi/UBISchemeCycle.test.ts index 591f2328..efcd14a7 100644 --- a/test/ubi/UBISchemeCycle.test.ts +++ b/test/ubi/UBISchemeCycle.test.ts @@ -13,19 +13,10 @@ const ONE_DAY = 86400; describe("UBIScheme cycle", () => { let goodDollar, firstClaimPool; let reputation; - let root, - acct, - claimer1, - claimer2, - claimer3, - signers, - nameService, - genericCall, - ubiScheme: UBIScheme; + let root, acct, claimer1, claimer2, claimer3, signers, nameService, genericCall, ubiScheme: UBIScheme; before(async () => { - [root, acct, claimer1, claimer2, claimer3, ...signers] = - await ethers.getSigners(); + [root, acct, claimer1, claimer2, claimer3, ...signers] = await ethers.getSigners(); const deployedDAO = await loadFixture(createDAO); let { @@ -64,9 +55,7 @@ describe("UBIScheme cycle", () => { it("should be able to change cycleLength if avatar", async () => { // initializing the ubi - let encodedCall = ubiScheme.interface.encodeFunctionData("setCycleLength", [ - 8 - ]); + let encodedCall = ubiScheme.interface.encodeFunctionData("setCycleLength", [8]); await genericCall(ubiScheme.address, encodedCall); expect(await ubiScheme.cycleLength()).to.be.equal(8); }); @@ -81,10 +70,7 @@ describe("UBIScheme cycle", () => { it("should set ubischeme", async () => { // initializing the ubi - let encodedCall = firstClaimPool.interface.encodeFunctionData( - "setUBIScheme", - [ubiScheme.address] - ); + let encodedCall = firstClaimPool.interface.encodeFunctionData("setUBIScheme", [ubiScheme.address]); await genericCall(firstClaimPool.address, encodedCall); // await firstClaimPool.start(); @@ -99,9 +85,7 @@ describe("UBIScheme cycle", () => { let currentCycle = await ubiScheme.currentCycleLength(); let dailyAmount = await ubiScheme.dailyUbi(); expect(currentCycle.toNumber()).to.be.gt(0); - const cycleEvent = transaction.events.find( - e => e.event === "UBICycleCalculated" - ); + const cycleEvent = transaction.events.find(e => e.event === "UBICycleCalculated"); console.log({ dailyAmount: dailyAmount.toString(), event: cycleEvent.args @@ -117,50 +101,40 @@ describe("UBIScheme cycle", () => { await ubiScheme.connect(claimer2).claim(); increaseTime(ONE_DAY); let transaction = await ubiScheme.connect(claimer2).claim(); - expect( - await goodDollar.balanceOf(claimer2.address).then(_ => _.toNumber()) - ).to.be.equal(125 + (await ubiScheme.dailyUbi().then(_ => _.toNumber()))); //first day 125 , second claim 125000 wei daily pool divided by 1000 active users = 125 - expect( - await ubiScheme.dailyCyclePool().then(_ => _.toNumber()) - ).to.be.equal(125000); - expect( - await ubiScheme.currentDayInCycle().then(_ => _.toNumber()) - ).to.be.equal(1); //1 day passed + expect(await goodDollar.balanceOf(claimer2.address).then(_ => _.toNumber())).to.be.equal( + 125 + (await ubiScheme.dailyUbi().then(_ => _.toNumber())) + ); //first day 125 , second claim 125000 wei daily pool divided by 1000 active users = 125 + expect(await ubiScheme.dailyCyclePool().then(_ => _.toNumber())).to.be.equal(125000); + expect(await ubiScheme.currentDayInCycle().then(_ => _.toNumber())).to.be.equal(1); //1 day passed }); it("should calculate next cycle even if day passed without claims(setDay)", async () => { increaseTime(ONE_DAY * 9); - expect( - await ubiScheme.currentDayInCycle().then(_ => _.toNumber()) - ).to.be.equal(10); //10 days passed total - + expect(await ubiScheme.currentDayInCycle().then(_ => _.toNumber())).to.be.equal(10); //10 days passed total + + const claimerBalanceBefore = await goodDollar.balanceOf(claimer1.address); + const minActiveUsers = await ubiScheme.minActiveUsers(); + let dailyClaimAmount = (await ubiScheme.dailyCyclePool()).div(minActiveUsers); //initialy we have by default min 1000 active users + const estimatedUbi = await ubiScheme.estimateNextDailyUBI(); + let totalClaimed = 0; + const currentDay = (await ubiScheme.currentDay()).toNumber(); + for (let i = currentDay - 1; i >= 0; i--) { + totalClaimed += (await ubiScheme.claimDay(i)).claimAmount.toNumber(); + } let transaction = await (await ubiScheme.connect(claimer1).claim()).wait(); //claims in new ubi cycle - let dailyClaimAmount = (await ubiScheme.dailyCyclePool()).div(1000); //initialy we have by default min 1000 active users - - expect(await goodDollar.balanceOf(claimer1.address)).to.be.equal( - dailyClaimAmount.add(125) - ); //intial 10 from first claim pool + daily - const cycleEvent = transaction.events.find( - e => e.event === "UBICycleCalculated" - ); + const cycleEvent = transaction.events.find(e => e.event === "UBICycleCalculated"); + expect(await goodDollar.balanceOf(claimer1.address)).to.be.equal(claimerBalanceBefore.add(dailyClaimAmount)); //intial 10 from first claim pool + daily + expect(dailyClaimAmount).eq(estimatedUbi); expect(cycleEvent).to.be.not.empty; - - expect( - await ubiScheme.currentDayInCycle().then(_ => _.toNumber()) - ).to.be.equal(0); //new cycle started + expect(await ubiScheme.currentDayInCycle().then(_ => _.toNumber())).to.be.equal(0); //new cycle started //intial balance on cycle start 1000000 - 375(3 user that claimed in previous tests) = 99625, divide by cycle length (8) = 124953 - expect(cycleEvent.args.dailyUBIPool).to.be.equal( - (BigInt(1000000) - BigInt(375)) / BigInt(8) - ); + expect(cycleEvent.args.dailyUBIPool).to.be.equal((BigInt(1000000) - BigInt(totalClaimed)) / BigInt(8)); }); it("should calculate cycle early if we can increase current daily pool", async () => { //increase ubi pool balance - let encoded = goodDollar.interface.encodeFunctionData("mint", [ - ubiScheme.address, - 400000 - ]); + let encoded = goodDollar.interface.encodeFunctionData("mint", [ubiScheme.address, 400000]); await genericCall(goodDollar.address, encoded); let balance = await goodDollar.balanceOf(ubiScheme.address); @@ -173,9 +147,7 @@ describe("UBIScheme cycle", () => { const estimated = await ubiScheme.estimateNextDailyUBI(); await increaseTime(ONE_DAY); //make sure let transaction = await (await ubiScheme.connect(claimer1).claim()).wait(); - const cycleEvent = transaction.events.find( - e => e.event === "UBICycleCalculated" - ); + const cycleEvent = transaction.events.find(e => e.event === "UBICycleCalculated"); const dailyUBI = await ubiScheme.dailyUbi(); expect(dailyUBI).to.eq(estimated); //the estimated before actual calculation should be correct, ie equal to actual dailyUBI calculated after first claim. expect(cycleEvent).to.be.not.empty; @@ -187,10 +159,7 @@ describe("UBIScheme cycle", () => { it("should not calculate cycle early if not possible to increase daily ubi pool", async () => { //increase ubi pool balance - let encoded = goodDollar.interface.encodeFunctionData("mint", [ - ubiScheme.address, - 100 - ]); + let encoded = goodDollar.interface.encodeFunctionData("mint", [ubiScheme.address, 100]); await genericCall(goodDollar.address, encoded); let balance = await goodDollar.balanceOf(ubiScheme.address); const curCycleLen = await ubiScheme.cycleLength(); @@ -200,9 +169,7 @@ describe("UBIScheme cycle", () => { await increaseTime(ONE_DAY); //make sure let transaction = await (await ubiScheme.connect(claimer1).claim()).wait(); - const cycleEvent = transaction.events.find( - e => e.event === "UBICycleCalculated" - ); + const cycleEvent = transaction.events.find(e => e.event === "UBICycleCalculated"); expect(cycleEvent).to.be.undefined; }); }); diff --git a/yarn.lock b/yarn.lock index ab95123c..185f1433 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1443,6 +1443,17 @@ __metadata: languageName: node linkType: hard +"@chainlink/env-enc@npm:^1.0.5": + version: 1.0.5 + resolution: "@chainlink/env-enc@npm:1.0.5" + dependencies: + yargs: ^17.7.1 + bin: + env-enc: dist/cli.js + checksum: dec4ab663de90b6aa110f137a5bf00f55c1224c62bad896e247a2ba026a5bd4505565799a73aac70509f06dc24a5f046b2c2798f5cb3cd8c2ce9f4cf12a4656b + languageName: node + linkType: hard + "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -2752,6 +2763,7 @@ __metadata: "@babel/preset-env": "*" "@babel/register": "*" "@celo-tools/celo-ethers-wrapper": ^0.4.0 + "@chainlink/env-enc": ^1.0.5 "@gnosis.pm/safe-core-sdk": ^3.2.0 "@gnosis.pm/safe-core-sdk-types": ^1.7.0 "@gnosis.pm/safe-ethers-lib": ^1.7.0 @@ -2768,7 +2780,7 @@ __metadata: "@openzeppelin/contracts": ^4.8.0 "@openzeppelin/contracts-upgradeable": ^4.8.0 "@openzeppelin/hardhat-upgrades": ^1.22.1 - "@safe-global/api-kit": ^3.0.1 + "@safe-global/api-kit": ^4.0.0 "@safe-global/protocol-kit": ^6.0.2 "@safe-global/types-kit": ^2.0.1 "@superfluid-finance/ethereum-contracts": 1.8.1 @@ -3760,19 +3772,19 @@ __metadata: languageName: node linkType: hard -"@safe-global/api-kit@npm:^3.0.1": - version: 3.0.1 - resolution: "@safe-global/api-kit@npm:3.0.1" +"@safe-global/api-kit@npm:^4.0.0": + version: 4.0.0 + resolution: "@safe-global/api-kit@npm:4.0.0" dependencies: - "@safe-global/protocol-kit": ^6.0.1 - "@safe-global/types-kit": ^2.0.0 + "@safe-global/protocol-kit": ^6.1.0 + "@safe-global/types-kit": ^3.0.0 node-fetch: ^2.7.0 viem: ^2.21.8 - checksum: e8a50ec6b7b818af5c400811d7c4ce918943676b476860dca9a0dd49943a38980708d7d44223de016e0748a6a879307ecc9928e10b83d2019c56bce3c5af58d3 + checksum: 187ce7a1ddba432cdc95c05064366128dd21e9948f5b14ac304c7aef230a3d3fc8e91861303f8ecfedeaf3805fc258184511062bdfd1150b42d65a3afb145adc languageName: node linkType: hard -"@safe-global/protocol-kit@npm:^6.0.1, @safe-global/protocol-kit@npm:^6.0.2": +"@safe-global/protocol-kit@npm:^6.0.2": version: 6.0.2 resolution: "@safe-global/protocol-kit@npm:6.0.2" dependencies: @@ -3793,6 +3805,27 @@ __metadata: languageName: node linkType: hard +"@safe-global/protocol-kit@npm:^6.1.0": + version: 6.1.1 + resolution: "@safe-global/protocol-kit@npm:6.1.1" + dependencies: + "@noble/curves": ^1.6.0 + "@peculiar/asn1-schema": ^2.3.13 + "@safe-global/safe-deployments": ^1.37.42 + "@safe-global/safe-modules-deployments": ^2.2.14 + "@safe-global/types-kit": ^3.0.0 + abitype: ^1.0.2 + semver: ^7.7.2 + viem: ^2.21.8 + dependenciesMeta: + "@noble/curves": + optional: true + "@peculiar/asn1-schema": + optional: true + checksum: 3999727d68a0504f5fd0e4ffa64fe83ff3d4ae80797106c52070cbd541f0bbaf6758f76ebf1161cce7c323d87d9e989e4aaaf8d9a3135101c1329c62443992ff + languageName: node + linkType: hard + "@safe-global/safe-deployments@npm:^1.37.31": version: 1.37.31 resolution: "@safe-global/safe-deployments@npm:1.37.31" @@ -3802,6 +3835,22 @@ __metadata: languageName: node linkType: hard +"@safe-global/safe-deployments@npm:^1.37.42": + version: 1.37.47 + resolution: "@safe-global/safe-deployments@npm:1.37.47" + dependencies: + semver: ^7.6.2 + checksum: 952ff6e6272622d2fcde1c5a5542bdb89613f6ddf9852f829f5961096b64fffe7d8f6924ad8ca1beaf970036242d11e4027b68c202b7d205dc9c4556750983d0 + languageName: node + linkType: hard + +"@safe-global/safe-modules-deployments@npm:^2.2.14": + version: 2.2.18 + resolution: "@safe-global/safe-modules-deployments@npm:2.2.18" + checksum: 4713a9deb5d39ffa1af273c427e7647cb8e81b6212281b9f6cb83fb69701114b2c3b0bb2e0e889ea8bc71860daa8dacd1082a20df5617677d235ffdfb4ab226b + languageName: node + linkType: hard + "@safe-global/safe-modules-deployments@npm:^2.2.7": version: 2.2.7 resolution: "@safe-global/safe-modules-deployments@npm:2.2.7" @@ -3809,7 +3858,7 @@ __metadata: languageName: node linkType: hard -"@safe-global/types-kit@npm:^2.0.0, @safe-global/types-kit@npm:^2.0.1": +"@safe-global/types-kit@npm:^2.0.1": version: 2.0.1 resolution: "@safe-global/types-kit@npm:2.0.1" dependencies: @@ -3818,6 +3867,15 @@ __metadata: languageName: node linkType: hard +"@safe-global/types-kit@npm:^3.0.0": + version: 3.0.0 + resolution: "@safe-global/types-kit@npm:3.0.0" + dependencies: + abitype: ^1.0.2 + checksum: 02f868e8a2de0681c637ed853473766468a4e4b9ee024c3d807b72bf0cbd3555593d88711c0ebd7b0cee159e8d61320542d659dd291cbff0829a2466b5b6fa3f + languageName: node + linkType: hard + "@scure/base@npm:~1.1.0": version: 1.1.1 resolution: "@scure/base@npm:1.1.1" @@ -8442,6 +8500,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + "clone-buffer@npm:1.0.0": version: 1.0.0 resolution: "clone-buffer@npm:1.0.0" @@ -19577,6 +19646,15 @@ patch-package@latest: languageName: node linkType: hard +"semver@npm:^7.7.2": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: f013a3ee4607857bcd3503b6ac1d80165f7f8ea94f5d55e2d3e33df82fce487aa3313b987abf9b39e0793c83c9fc67b76c36c067625141a9f6f704ae0ea18db2 + languageName: node + linkType: hard + "semver@npm:~5.4.1": version: 5.4.1 resolution: "semver@npm:5.4.1" @@ -24371,6 +24449,13 @@ patch-package@latest: languageName: node linkType: hard +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c + languageName: node + linkType: hard + "yargs-unparser@npm:1.6.0": version: 1.6.0 resolution: "yargs-unparser@npm:1.6.0" @@ -24427,6 +24512,21 @@ patch-package@latest: languageName: node linkType: hard +"yargs@npm:^17.7.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: ^8.0.1 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a + languageName: node + linkType: hard + "yargs@npm:^4.7.1": version: 4.8.1 resolution: "yargs@npm:4.8.1" From 317ea5e4720d30de34d8c969874a0db18e0cc7b1 Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 6 Nov 2025 12:22:19 +0200 Subject: [PATCH 11/13] add: env-enc --- hardhat.config.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index c6753ed1..6aeaf12d 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -23,6 +23,9 @@ import { sumStakersGdRewards } from "./scripts/staking/stakersGdRewardsCalculati import { verify } from "./scripts/verify"; import { ethers } from "ethers"; import { fstat, readFileSync, writeFileSync } from "fs"; +import * as envEnc from "@chainlink/env-enc"; +envEnc.config(); + config(); const mnemonic = process.env.MNEMONIC || "test test test test test test test test test test test junk"; @@ -38,7 +41,7 @@ const MAINNET_URL = "https://mainnet.infura.io/v3/" + infura_api; const xdc = { accounts: { mnemonic }, - url: "https://rpc.xdc.network", + url: "https://rpc.ankr.com/xdc/ef07ba6590dc46db9275bba237aed203ed6d5fb3e3203ff237a82a841f75b2ce", gas: 3000000, gasPrice: 12.5e9, chainId: 50 From 98d4d23c907e8a333e2b2dee31cd5f1f2ef4dc8f Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 6 Nov 2025 12:53:04 +0200 Subject: [PATCH 12/13] fix: claimcycle unit test --- test/ubi/UBISchemeCycle.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ubi/UBISchemeCycle.test.ts b/test/ubi/UBISchemeCycle.test.ts index efcd14a7..4d010fb9 100644 --- a/test/ubi/UBISchemeCycle.test.ts +++ b/test/ubi/UBISchemeCycle.test.ts @@ -118,7 +118,7 @@ describe("UBIScheme cycle", () => { const estimatedUbi = await ubiScheme.estimateNextDailyUBI(); let totalClaimed = 0; const currentDay = (await ubiScheme.currentDay()).toNumber(); - for (let i = currentDay - 1; i >= 0; i--) { + for (let i = currentDay; i >= 0; i--) { totalClaimed += (await ubiScheme.claimDay(i)).claimAmount.toNumber(); } let transaction = await (await ubiScheme.connect(claimer1).claim()).wait(); //claims in new ubi cycle From c2d7ce3730d030ee50825fdd2d3b749009575aa4 Mon Sep 17 00:00:00 2001 From: sirpy Date: Thu, 6 Nov 2025 14:04:49 +0200 Subject: [PATCH 13/13] add: remove need for specific pool fee --- contracts/IUniswapV3.sol | 2 + .../mocks/GenericDistributionHelperTest.sol | 31 ++ .../reserve/GenericDistributionHelper.sol | 48 +-- .../reserve/GenericDistributionHelper.test.ts | 315 ++++++++++++++++++ 4 files changed, 376 insertions(+), 20 deletions(-) create mode 100644 contracts/mocks/GenericDistributionHelperTest.sol create mode 100644 test/reserve/GenericDistributionHelper.test.ts diff --git a/contracts/IUniswapV3.sol b/contracts/IUniswapV3.sol index c3c9a143..b5c9e592 100644 --- a/contracts/IUniswapV3.sol +++ b/contracts/IUniswapV3.sol @@ -8,6 +8,8 @@ interface IUniswapV3Pool { function token1() external view returns (address); + function fee() external view returns (uint24); + function slot0() external view diff --git a/contracts/mocks/GenericDistributionHelperTest.sol b/contracts/mocks/GenericDistributionHelperTest.sol new file mode 100644 index 00000000..14aa6d8a --- /dev/null +++ b/contracts/mocks/GenericDistributionHelperTest.sol @@ -0,0 +1,31 @@ +pragma solidity >=0.8; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; + +import "../reserve/GenericDistributionHelper.sol"; + +contract GenericDistributionHelperTest is GenericDistributionHelper { + function onDistribution(uint256 _amount) external override { + revert(); + } +} + +contract GenericDistributionHelperTestHelper is GenericDistributionHelper { + IMessagePassingBridge bridge; + + function setOracle(IStaticOracle oracle) external { + STATIC_ORACLE = oracle; + } + + function getBridge() public view override returns (IMessagePassingBridge) { + return bridge; + } + + function setBridges(address _mpbBridge) external { + bridge = IMessagePassingBridge(_mpbBridge); + } +} diff --git a/contracts/reserve/GenericDistributionHelper.sol b/contracts/reserve/GenericDistributionHelper.sol index 38be2267..8fd62ed2 100644 --- a/contracts/reserve/GenericDistributionHelper.sol +++ b/contracts/reserve/GenericDistributionHelper.sol @@ -9,6 +9,7 @@ import "@gooddollar/bridge-contracts/contracts/messagePassingBridge/IMessagePass import "@uniswap/v2-periphery/contracts/interfaces/IWETH.sol"; import "../utils/DAOUpgradeableContract.sol"; +import "../IUniswapV3.sol"; // import "hardhat/console.sol"; @@ -94,7 +95,7 @@ contract GenericDistributionHelper is _setReserveToken(_reserveToken); } - function getBridge() public view returns (IMessagePassingBridge) { + function getBridge() public view virtual returns (IMessagePassingBridge) { return IMessagePassingBridge(nameService.getAddress("MPBBRIDGE_CONTRACT")); } @@ -105,19 +106,15 @@ contract GenericDistributionHelper is } function _setReserveToken(address _reserveToken) internal { - uint24[] memory fees = new uint24[](1); - fees[0] = 100; reserveToken = _reserveToken; - STATIC_ORACLE.prepareSpecificFeeTiersWithTimePeriod( + STATIC_ORACLE.prepareAllAvailablePoolsWithTimePeriod( reserveToken, address(nativeToken()), - fees, 60 ); - STATIC_ORACLE.prepareSpecificFeeTiersWithTimePeriod( + STATIC_ORACLE.prepareAllAvailablePoolsWithTimePeriod( reserveToken, gasToken, - fees, 60 ); } @@ -243,25 +240,21 @@ contract GenericDistributionHelper is function calcGDToSell( uint256 maxAmountToSell ) public view returns (uint256 gdToSell, uint256 minReceived) { - uint24[] memory fees = new uint24[](1); - fees[0] = 100; uint256 nativeToBuy = feeSettings.minBalanceForFees * 3 - address(this).balance; (uint256 nativeValueInUSD, ) = STATIC_ORACLE - .quoteSpecificFeeTiersWithTimePeriod( + .quoteAllAvailablePoolsWithTimePeriod( uint128(nativeToBuy), gasToken, reserveToken, - fees, 60 //last 1 minute ); - (gdToSell, ) = STATIC_ORACLE.quoteSpecificFeeTiersWithTimePeriod( + (gdToSell, ) = STATIC_ORACLE.quoteAllAvailablePoolsWithTimePeriod( uint128(nativeValueInUSD), reserveToken, address(nativeToken()), - fees, 60 //last 1 minute ); @@ -269,22 +262,19 @@ contract GenericDistributionHelper is if (gdToSell > maxAmountToSell) { gdToSell = maxAmountToSell; - fees[0] = 100; // gdToSell = (nativeValueInUSD * 1e18) / gdPriceInUSD; // mul by 1e18 so result is in 18 decimals (uint256 minReceivedCUSD, ) = STATIC_ORACLE - .quoteSpecificFeeTiersWithTimePeriod( + .quoteAllAvailablePoolsWithTimePeriod( uint128(gdToSell), address(nativeToken()), reserveToken, - fees, 60 //last 1 minute ); - (minReceived, ) = STATIC_ORACLE.quoteSpecificFeeTiersWithTimePeriod( + (minReceived, ) = STATIC_ORACLE.quoteAllAvailablePoolsWithTimePeriod( uint128(minReceivedCUSD), reserveToken, gasToken, - fees, 60 //last 1 minute ); } @@ -294,15 +284,33 @@ contract GenericDistributionHelper is uint256 amountToSell, uint256 minReceived ) internal returns (uint256 nativeBought) { + address[] memory gdPools = STATIC_ORACLE.getAllPoolsForPair( + reserveToken, + address(nativeToken()) + ); + address[] memory gasPools = STATIC_ORACLE.getAllPoolsForPair( + reserveToken, + gasToken + ); + uint24 gasFee = IUniswapV3Pool(gasPools[0]).fee(); + uint24 gdFee = IUniswapV3Pool(gdPools[0]).fee(); + for (uint i = 1; i < gasPools.length; i++) { + uint24 fee = IUniswapV3Pool(gasPools[i]).fee(); + gasFee = gasFee < fee ? gasFee : fee; + } + for (uint i = 1; i < gdPools.length; i++) { + uint24 fee = IUniswapV3Pool(gdPools[i]).fee(); + gdFee = gasFee < fee ? gasFee : fee; + } ERC20(nativeToken()).approve(address(ROUTER), amountToSell); uint256 amountOutMinimum = (minReceived * (100 - feeSettings.maxSlippage)) / 100; // 5% slippage ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({ path: abi.encodePacked( nativeToken(), - uint24(100), + gdFee, reserveToken, - uint24(100), + gasFee, gasToken ), recipient: address(this), diff --git a/test/reserve/GenericDistributionHelper.test.ts b/test/reserve/GenericDistributionHelper.test.ts new file mode 100644 index 00000000..15e64710 --- /dev/null +++ b/test/reserve/GenericDistributionHelper.test.ts @@ -0,0 +1,315 @@ +import { ethers, upgrades, artifacts } from "hardhat"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { GoodReserveCDai, DistributionBridgeMock, IGoodDollar, GenericDistributionHelperTestHelper } from "../../types"; +import { createDAO, increaseTime } from "../helpers"; +import * as waffle from "ethereum-waffle"; + +const BN = ethers.BigNumber; +export const NULL_ADDRESS = ethers.constants.AddressZero; +export const BLOCK_INTERVAL = 1; + +describe("GenericDistributionHelper", () => { + let goodReserve: GoodReserveCDai; + let goodDollar: IGoodDollar, genericCall, avatar, founder, signers, setDAOAddress, nameService, cDai; + + before(async () => { + [founder, ...signers] = await ethers.getSigners(); + let { + controller: ctrl, + avatar: av, + gd, + identity, + setDAOAddress: sda, + setSchemes, + reserve, + cdaiAddress, + genericCall: gc, + nameService: ns + } = await loadFixture(createDAO); + + nameService = ns; + genericCall = gc; + cDai = cdaiAddress; + avatar = av; + setDAOAddress = sda; + + console.log("deployed dao", { + founder: founder.address, + gd, + identity, + avatar + }); + + goodDollar = (await ethers.getContractAt("IGoodDollar", gd)) as IGoodDollar; + + console.log("deployed contribution, deploying reserve...", { + founder: founder.address + }); + + goodReserve = reserve as GoodReserveCDai; + }); + + const fixture = async () => { + const mpbf = await artifacts.readArtifact("IStaticOracle"); + + let oracle = await waffle.deployMockContract(founder, mpbf.abi); + oracle.mock.quoteAllAvailablePoolsWithTimePeriod.returns(1e13, []); + oracle.mock.prepareAllAvailablePoolsWithTimePeriod.returns([]); + + const distHelper = (await upgrades.deployProxy( + await ethers.getContractFactory("GenericDistributionHelperTestHelper"), + [ + nameService.address, + oracle.address, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + ethers.constants.AddressZero, + { + maxFee: "5000000000000000", + minBalanceForFees: "10000000000000000", + percentageToSellForFee: "5", + maxSlippage: "5" + } + ], + { kind: "uups" } + )) as GenericDistributionHelperTestHelper; + + //make sure disthelper has enough native token so it doesnt try to swap G$s + await founder.sendTransaction({ + to: distHelper.address, + value: ethers.constants.WeiPerEther + }); + const bridge = (await ethers.deployContract("DistributionBridgeMock")) as DistributionBridgeMock; + + const encodedCall = distHelper.interface.encodeFunctionData("setFeeSettings", [ + { + maxFee: "5000000000000000", + minBalanceForFees: "10000000000000000", + percentageToSellForFee: "5", + maxSlippage: "5" + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + await distHelper.setBridges(bridge.address); + + return { distHelper, bridge, oracle }; + }; + + it("should not allow to add recipient", async () => { + const { distHelper } = await loadFixture(fixture); + + const recipient = signers[0]; + + await expect( + distHelper.addOrUpdateRecipient({ + bps: 3000, + chainId: 42220, + addr: recipient.address, + transferType: 0 + }) + ).to.be.revertedWith(/is missing role 0x0000000000000000000000000000000000000000000000000000000000000000/); + }); + + it("should allow to add recipient by avatar", async () => { + const { distHelper } = await loadFixture(fixture); + + const recipient = signers[0]; + + const encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 3000, + chainId: 42220, + addr: recipient.address, + transferType: 0 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + const dr = await distHelper.distributionRecipients(0); + expect(dr.addr).to.equal(recipient.address); + expect(dr.chainId).to.equal(42220); + expect(dr.bps).to.equal(3000); + expect(dr.transferType).to.equal(0); + }); + + it("should allow to update recipient by avatar", async () => { + const { distHelper } = await loadFixture(fixture); + + const recipient = signers[0]; + + let encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 1000, + chainId: 4447, + addr: recipient.address, + transferType: 1 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + + encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 1500, + chainId: 45, + addr: recipient.address, + transferType: 0 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + + const updEvents = await distHelper.queryFilter(distHelper.filters.RecipientUpdated()); + const addEvents = await distHelper.queryFilter(distHelper.filters.RecipientAdded()); + + const dr = await distHelper.distributionRecipients(0); + expect(dr.addr).to.equal(recipient.address); + expect(dr.chainId).to.equal(45); + expect(dr.bps).to.equal(1500); + expect(dr.transferType).to.equal(0); + }); + + it("should distribute via layerzero bridge", async () => { + const { distHelper, bridge } = await loadFixture(fixture); + + const recipient = signers[0]; + + let encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 2000, + chainId: 42220, + addr: recipient.address, + transferType: 0 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + + await goodDollar.mint(distHelper.address, "100000000000"); + await founder.sendTransaction({ + to: distHelper.address, + value: ethers.constants.WeiPerEther + }); + await distHelper.onDistribution("100000000000"); + expect(await goodDollar.allowance(distHelper.address, bridge.address)).to.equal((100000000000 * 2000) / 10000); + + const events = await bridge.queryFilter(bridge.filters.BridgeLz()); + expect(events[0].args.recipient).to.equal(recipient.address); + expect(events[0].args.amount).to.equal((100000000000 * 2000) / 10000); + expect(events[0].args.chainId).to.equal(42220); + expect(events[0].args.fee).to.equal(5e14); + }); + + it("should distribute via transferAndCall", async () => { + const { distHelper, bridge } = await loadFixture(fixture); + + const recipient = signers[0]; + + let encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 2555, + chainId: 4447, + addr: signers[0].address, + transferType: 2 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + + await goodDollar.mint(distHelper.address, "100000000000"); + await distHelper.onDistribution("100000000000"); + expect(await goodDollar.balanceOf(signers[0].address)).to.equal((100000000000 * 2555) / 10000); + }); + + it("should distribute to multiple recipients", async () => { + const { distHelper, bridge } = await loadFixture(fixture); + + const recipient = signers[0]; + + let encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 2555, + chainId: 4447, + addr: signers[0].address, + transferType: 1 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + + encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 1000, + chainId: 4447, + addr: signers[1].address, + transferType: 1 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + + encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 5, + chainId: 4447, + addr: signers[2].address, + transferType: 2 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + + await goodDollar.mint(distHelper.address, "100000000000"); + await distHelper.onDistribution("100000000000"); + expect(await goodDollar.balanceOf(signers[0].address)).to.equal((100000000000 * 2555) / 10000); + expect(await goodDollar.balanceOf(signers[1].address)).to.equal((100000000000 * 1000) / 10000); + expect(await goodDollar.balanceOf(signers[2].address)).to.equal((100000000000 * 5) / 10000); + }); + + it("should emit distribution event for multiple recipients", async () => { + const { distHelper, bridge } = await loadFixture(fixture); + + let encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 2555, + chainId: 122, + addr: signers[0].address, + transferType: 0 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + + encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 1000, + chainId: 4447, + addr: signers[1].address, + transferType: 1 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + + encodedCall = distHelper.interface.encodeFunctionData("addOrUpdateRecipient", [ + { + bps: 5, + chainId: 4447, + addr: signers[2].address, + transferType: 2 + } + ]); + + await genericCall(distHelper.address, encodedCall, avatar.address, 0); + + await goodDollar.mint(distHelper.address, "100000000000"); + await distHelper.onDistribution("100000000000"); + + const DistributionEvents = await distHelper.queryFilter(distHelper.filters.Distribution()); + expect(DistributionEvents[0].args.distributionRecipients[0].addr).eq(signers[0].address); + expect(DistributionEvents[0].args.distributionRecipients[1].addr).eq(signers[1].address); + expect(DistributionEvents[0].args.distributionRecipients[2].addr).eq(signers[2].address); + }); +});