From 45840ba5337da7d0810ac9608f52d380ce9ba1f6 Mon Sep 17 00:00:00 2001 From: James Duncombe Date: Mon, 4 Mar 2024 18:31:12 +0000 Subject: [PATCH] Updates Paymaster tasks. --- tasks/paymaster.ts | 243 ++++++++++++++++++++++++++++++--------------- 1 file changed, 164 insertions(+), 79 deletions(-) diff --git a/tasks/paymaster.ts b/tasks/paymaster.ts index 7e926ed3..01b47124 100644 --- a/tasks/paymaster.ts +++ b/tasks/paymaster.ts @@ -1,8 +1,8 @@ import { task, types } from "hardhat/config"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { ZERO_ADDRESS, deploymentSalt } from "../src/utils"; -import { Paymaster } from "../typechain/hardhat-diamond-abi/HardhatDiamondABI.sol"; -import { RelayHub, StakeManager } from "@opengsn/contracts"; +import { Marketplace, Issuer, Paymaster } from "../typechain/hardhat-diamond-abi/HardhatDiamondABI.sol"; +import { RelayHub, RelayHub__factory, StakeManager, StakeManager__factory } from "@opengsn/contracts"; import { BigNumber } from "ethers"; import { IERC20 } from "../typechain"; @@ -38,75 +38,7 @@ task("paymaster-update-facets", "Updates facets of our Paymaster") }); }); -/** - * This should only be called in development environments. - * I've prefixed this task for now... - * TODO: Split this up... some is for the dev environment and some is for the mainnet environment. -*/ - -interface PaymasterStakeParams { - readonly amount: BigNumber; - readonly stakeTokenAddress: string; - readonly relayHubAddress: string; - readonly relayManagerAddress: string; - readonly relayWorkerAddress: string; - readonly stakeManagerAddress: string; -} - -task("dev-paymaster-relay-setup", "Setup task for the relay") - .addParam("stakeTokenAddress", "The token to stake with", undefined, types.string) - .addParam("stakeManagerAddress", "The stake manager address", undefined, types.string) - .addParam("relayManagerAddress", "The relay manager address", undefined, types.string) - .addParam("relayWorkerAddress", "The relay worker address", undefined, types.string) - .addParam("relayHubAddress", "The RelayHub address", undefined, types.string) - .addParam("amount", "The amount of ETH to stake the RelayHub with", undefined, types.int) - .setAction(async (params: PaymasterStakeParams, hre) => { - const { ethers, getNamedAccounts } = hre; - - // Why user1? - // We're using user1 as it's the owner of the relay hub etc. - const { user1 } = await getNamedAccounts(); - const ownerSigner = await ethers.getSigner(user1); - - const seedAmountForRelayAccounts = ethers.utils.parseEther("0.5"); - - console.log("Depositing ETH/MATIC to relay manager and worker accounts..."); - await (await ownerSigner.sendTransaction({ to: params.relayManagerAddress, value: seedAmountForRelayAccounts })).wait(); - await (await ownerSigner.sendTransaction({ to: params.relayWorkerAddress, value: seedAmountForRelayAccounts })).wait(); - - // Get a handle to the stake token. - const stakeToken = await ethers.getContractAt("IERC20", params.stakeTokenAddress); - - console.log("Approving the stake token..."); - await (await stakeToken.connect(ownerSigner).approve( - params.stakeManagerAddress, params.amount - )).wait(); - - // We can't use the deployment artifact etc for the ABI so manually adding it here. - const stakeManager = await ethers.getContractAt([ - "function stakeForRelayManager(address, address, uint256, uint256) external", - "function authorizeHubByOwner(address relayManager, address relayHub) external" - ], params.stakeManagerAddress); - - const stakeManagerAsOwner = stakeManager.connect(ownerSigner); - - console.log("Authorizing hub..."); - await (await stakeManagerAsOwner.authorizeHubByOwner( - params.relayManagerAddress, - params.relayHubAddress - )).wait(); - - console.log("Staking for relay manager..."); - await (await stakeManagerAsOwner.stakeForRelayManager( - params.stakeTokenAddress, - params.relayManagerAddress, - /* unstakeDelay */ 7 * 24 * 3600, - params.amount - )).wait(); - - console.log("Setup for the relay complete... 🙌"); - }); - +// Specific tasks for the paymaster - funding etc. interface PaymasterSetupParams { readonly relayHubAddress: string; @@ -117,7 +49,6 @@ task("paymaster-setup", "Sets up the Paymaster") .addParam("relayHubAddress", "The relay hub address", undefined, types.string) .addParam("trustedForwarderAddress", "The trusted forwarder address", undefined, types.string) .setAction(async (params: PaymasterSetupParams, hre) => { - const { ethers, getNamedAccounts } = hre; const { issuerMember } = await getNamedAccounts(); const issuerMemberSigner = await ethers.getSigner(issuerMember); @@ -129,16 +60,15 @@ task("paymaster-setup", "Sets up the Paymaster") console.log("Setting trusted forwarder and relay address..."); await (await issuerPaymaster.setRelayHub(params.relayHubAddress)).wait(); await (await issuerPaymaster.setTrustedForwarder(params.trustedForwarderAddress)).wait(); - console.log("... done"); }); interface PaymasterFundParams { readonly amount: BigNumber; } -// Why types.string? Because otherwise this will hit the max safe int size in JS. +// Why types.string for amount? Because otherwise this will hit the max safe int size in JS. task("paymaster-fund", "Funds the Paymaster") - .addParam("amount", "The amount of ETH to fund the Paymaster with", undefined, types.string) + .addParam("amount", "The amount of ETH to fund the Paymaster with (wei)", undefined, types.string) .setAction(async (params: PaymasterFundParams, hre) => { const { ethers, getNamedAccounts } = hre; const { issuerMember } = await getNamedAccounts(); @@ -155,10 +85,11 @@ task("paymaster-fund", "Funds the Paymaster") throw Error("Paymaster's RelayHub address has not been set!"); } - // We don't have access to a deployment artifact for the relay hub so manually adding the ABI here... - const relayHub = await ethers.getContractAt([ - "function balanceOf(address owner) public view returns (uint256)" - ], relayHubAddress); + // Get a handle to the relay hub. + const relayHub = await ethers.getContractAt( + abiFromFactory(RelayHub__factory), + relayHubAddress + ); console.log(`Funding RelayHub ${relayHubAddress} with ${params.amount} MATIC...`); @@ -169,6 +100,154 @@ task("paymaster-fund", "Funds the Paymaster") console.log(`Admin wallet balance: ${await issuerMemberSigner.getBalance()}`); }); +interface PaymasterStakeSetupParams { + readonly amount: BigNumber; + readonly stakeTokenAddress: string; + readonly relayHubAddress: string; + readonly relayManagerAddress: string; +} + +// Why types.string for amount? Because otherwise this will hit the max safe int size in JS. +task("paymaster-stake-setup", "Stakes the Relay") + .addParam("stakeTokenAddress", "The token to stake with", undefined, types.string) + .addParam("relayManagerAddress", "The relay manager address", undefined, types.string) + .addParam("relayHubAddress", "The RelayHub address", undefined, types.string) + .addParam("amount", "The amount of ETH to stake the RelayHub with (wei)", undefined, types.string) + .setAction(async (params: PaymasterStakeSetupParams, hre) => { + const { ethers, getNamedAccounts } = hre; + + // Why user1? + // We're using user1 as it's the owner of the Relay Hub. + const { user1 } = await getNamedAccounts(); + const ownerSigner = await ethers.getSigner(user1); + + // Get a handle to the stake token. + const stakeToken = await ethers.getContractAt("IERC20", params.stakeTokenAddress); + + // Get a handle to the relay hub. + const relayHub = await ethers.getContractAt( + abiFromFactory(RelayHub__factory), + params.relayHubAddress + ); + const relayHubAsOwner = relayHub.connect(ownerSigner); + + console.log("Getting the stake manager address..."); + const stakeManagerAddress = await relayHubAsOwner.getStakeManager(); + + console.log(`Approving the stake token using stake manager at: ${stakeManagerAddress}...`); + await (await stakeToken.connect(ownerSigner).approve( + stakeManagerAddress, params.amount + )).wait(); + + // Get the configuration and stake info. + const config = await relayHubAsOwner.getConfiguration(); + const minimumUnstakeDelay = config.minimumUnstakeDelay; + + // Get a handle to the stake manager. + const stakeManager = await ethers.getContractAt( + abiFromFactory(StakeManager__factory), + stakeManagerAddress + ); + const stakeManagerAsOwner = stakeManager.connect(ownerSigner); + + // Using the minimum unstake delay as the unstake delay for now. + console.log(`Staking for relay manager at: ${params.relayManagerAddress} with ${params.amount}...`); + await (await stakeManagerAsOwner.stakeForRelayManager( + params.stakeTokenAddress, + params.relayManagerAddress, + minimumUnstakeDelay, + params.amount + )).wait(); + + // TODO: Check if the hub is already authorized + + console.log(`Authorizing hub at: ${params.relayHubAddress}...`); + await (await stakeManagerAsOwner.authorizeHubByOwner( + params.relayManagerAddress, + params.relayHubAddress + )).wait(); + + console.log("Staking the Relay complete..."); + }); + +/** + * This should only be called in development environments. +*/ + +interface DevPaymasterFundRelayParams { + readonly relayManagerAddress: string; + readonly relayWorkerAddress: string; +} + +task("dev-fund-manager-and-worker", "Fund the manager and worker accounts") + .addParam("relayManagerAddress", "The relay manager address", undefined, types.string) + .addParam("relayWorkerAddress", "The relay worker address", undefined, types.string) + .setAction(async (params: DevPaymasterFundRelayParams, hre) => { + // Guard for non-dev environments. + const { name: netName } = hre.network; + if (netName !== "dev") { + throw new Error("This task should only be called in development environments."); + } + + const { ethers, getNamedAccounts } = hre; + + // Why user1? + // We're using user1 as it's the owner of the relay hub etc. + const { user1 } = await getNamedAccounts(); + const ownerSigner = await ethers.getSigner(user1); + + console.log("Owner address: ", ownerSigner.address); + + const seedAmountForRelayAccounts = ethers.utils.parseEther("0.2"); + + console.log("Depositing ETH/MATIC to relay manager and worker accounts..."); + console.log(`Relay manager at: ${params.relayManagerAddress}...`); + await (await ownerSigner.sendTransaction({ to: params.relayManagerAddress, value: seedAmountForRelayAccounts })).wait(); + console.log(`Relay worker at: ${params.relayWorkerAddress}...`); + await (await ownerSigner.sendTransaction({ to: params.relayWorkerAddress, value: seedAmountForRelayAccounts })).wait(); + }); + +/** +* This should only be called in development environments. +*/ + +interface DevPaymasterAddMembersParams { + readonly trustedForwarderAddress: string; + readonly gaslessAccounts: string[]; +} + +task("dev-paymaster-add-members", "Adds members to the marketplace etc - for dev testing") + .addParam("trustedForwarderAddress", "The relay manager address", undefined, types.string) + .addParam("gaslessAccounts", "The relay manager address", undefined, types.string) + .setAction(async (params: DevPaymasterAddMembersParams, hre) => { + // Guard for non-dev environments. + const { name: netName } = hre.network; + if (netName !== "dev") { + throw new Error("This task should only be called in development environments."); + } + + console.log("Adding members to the issuer and marketplace contracts..."); + + const { ethers, getNamedAccounts } = hre; + const { issuerMember } = await getNamedAccounts(); + const issuerMemberSigner = await ethers.getSigner(issuerMember); + + const issuer = await ethers.getContract("Issuer"); + const issuerIssuer = issuer.connect(issuerMemberSigner); + + const marketplace = await ethers.getContract("Marketplace"); + const issuerMarketplace = marketplace.connect(issuerMemberSigner); + + await Promise.all(params.gaslessAccounts.map(async (gaslessAccount) => { + await (await issuerIssuer.addMember(gaslessAccount)).wait(); + await (await issuerMarketplace.addMember(gaslessAccount)).wait(); + })); + + console.log("Adding the trusted forwarder...", params.trustedForwarderAddress); + + await (await issuerMarketplace.setTrustedForwarder(params.trustedForwarderAddress)).wait(); + }); + // Reusable functions and constants. const PAYMASTER_FACETS = [ @@ -212,4 +291,10 @@ const deployPaymaster = async ( return await ethers.getContract("Paymaster"); }; +// TODO: Tidy this up... any is too slack. +const abiFromFactory = (factory: any): [string] => { + return new ethers.utils.Interface(factory.abi) + .format(ethers.utils.FormatTypes.simple); +}; + export { PAYMASTER_FACETS, deployPaymaster }; \ No newline at end of file