From c3891aa4bd75f3ccb1e776691d8fe92d043a7bbd Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Wed, 15 May 2024 23:09:01 +0100 Subject: [PATCH] test: staking dispenser --- contracts/Dispenser.sol | 142 +++++++++------- contracts/test/MockVoteWeighting.sol | 82 +++++++++ hardhat.config.js | 2 +- test/DispenserDevIncentives.js | 14 +- test/DispenserStakingIncentives.js | 239 +++++++++++++++++++++++++++ test/StakingBridging.js | 3 +- 6 files changed, 409 insertions(+), 73 deletions(-) create mode 100644 contracts/test/MockVoteWeighting.sol create mode 100644 test/DispenserStakingIncentives.js diff --git a/contracts/Dispenser.sol b/contracts/Dispenser.sol index 07dc2815..a68b2301 100644 --- a/contracts/Dispenser.sol +++ b/contracts/Dispenser.sol @@ -284,20 +284,22 @@ contract Dispenser { mapping(uint256 => uint256) public mapChainIdWithheldAmounts; /// @dev Dispenser constructor. + /// @param _olas OLAS token address. /// @param _tokenomics Tokenomics address. /// @param _treasury Treasury address. /// @param _voteWeighting Vote Weighting address. - constructor(address _tokenomics, address _treasury, address _voteWeighting) { + constructor(address _olas, address _tokenomics, address _treasury, address _voteWeighting) { owner = msg.sender; _locked = 1; // TODO Define final behavior before deployment paused = Pause.StakingIncentivesPaused; // Check for at least one zero contract address - if (_tokenomics == address(0) || _treasury == address(0) || _voteWeighting == address(0)) { + if (_tokenomics == address(0) || _treasury == address(0) || _olas == address(0) || _voteWeighting == address(0)) { revert ZeroAddress(); } + olas = _olas; tokenomics = _tokenomics; treasury = _treasury; voteWeighting = _voteWeighting; @@ -317,7 +319,12 @@ contract Dispenser { uint256 numClaimedEpochs ) internal returns (uint256 firstClaimedEpoch, uint256 lastClaimedEpoch) { // Checkpoint the vote weighting for the retainer on L1 - IVoteWeighting(voteWeighting).checkpointNominee(target, chainId); + if (msg.sender == address(this) || msg.sender == owner) { + IVoteWeighting(voteWeighting).checkpointNominee(target, chainId); + } else { + voteWeighting.staticcall(abi.encodeWithSelector(IVoteWeighting(voteWeighting).checkpointNominee.selector, + target, chainId)); + } // Get the current epoch number uint256 eCounter = ITokenomics(tokenomics).epochCounter(); @@ -365,7 +372,9 @@ contract Dispenser { } // Write last claimed epoch counter to start claiming / retaining from the next time - mapLastClaimedStakingEpochs[nomineeHash] = lastClaimedEpoch; + if (msg.sender == address(this) || msg.sender == owner) { + mapLastClaimedStakingEpochs[nomineeHash] = lastClaimedEpoch; + } } /// @dev Distributes staking incentives to a corresponding staking target. @@ -641,6 +650,36 @@ contract Dispenser { } } + /// @dev Sets deposit processor contracts addresses and L2 chain Ids. + /// @notice It is the contract owner responsibility to set correct L1 deposit processor contracts + /// and corresponding supported L2 chain Ids. + /// @param depositProcessors Set of deposit processor contract addresses on L1. + /// @param chainIds Set of corresponding L2 chain Ids. + function setDepositProcessorChainIds(address[] memory depositProcessors, uint256[] memory chainIds) external { + // Check for the ownership + if (msg.sender != owner) { + revert OwnerOnly(msg.sender, owner); + } + + // Check for array length correctness + if (depositProcessors.length == 0 || depositProcessors.length != chainIds.length) { + revert WrongArrayLength(depositProcessors.length, chainIds.length); + } + + // Link L1 and L2 bridge mediators, set L2 chain Ids + for (uint256 i = 0; i < chainIds.length; ++i) { + // Check supported chain Ids on L2 + if (chainIds[i] == 0) { + revert ZeroValue(); + } + + // Note: depositProcessors[i] might be zero if there is a need to stop processing a specific L2 chain Id + mapChainIdDepositProcessors[chainIds[i]] = depositProcessors[i]; + } + + emit SetDepositProcessorChainIds(depositProcessors, chainIds); + } + /// @dev Records nominee starting epoch number. /// @param nomineeHash Nominee hash. function addNominee(bytes32 nomineeHash) external { @@ -649,6 +688,12 @@ contract Dispenser { revert ManagerOnly(msg.sender, voteWeighting); } + // Check for the paused state + Pause currentPause = paused; + if (currentPause == Pause.StakingIncentivesPaused || currentPause == Pause.AllPaused) { + revert Paused(); + } + mapLastClaimedStakingEpochs[nomineeHash] = ITokenomics(tokenomics).epochCounter(); } @@ -663,39 +708,6 @@ contract Dispenser { mapRemovedNomineeEpochs[nomineeHash] = ITokenomics(tokenomics).epochCounter(); } - /// @dev Retains staking incentives according to the retainer address to return it back to the staking inflation. - function retain() external { - // Go over epochs and retain funds to return back to the tokenomics - bytes32 localRetainer = retainer; - - // Check for zero retainer address - if (localRetainer == 0) { - revert ZeroAddress(); - } - - (uint256 firstClaimedEpoch, uint256 lastClaimedEpoch) = - _checkpointNomineeAndGetClaimedEpochCounters(localRetainer, block.chainid, maxNumClaimingEpochs); - - uint256 totalReturnAmount; - - // Traverse all the claimed epochs - for (uint256 j = firstClaimedEpoch; j < lastClaimedEpoch; ++j) { - // Get service staking info - ITokenomics.StakingPoint memory stakingPoint = - ITokenomics(tokenomics).mapEpochStakingPoints(j); - - // Get epoch end time - uint256 endTime = ITokenomics(tokenomics).getEpochEndTime(j); - - // Get the staking weight for each epoch - (uint256 stakingWeight, ) = - IVoteWeighting(voteWeighting).nomineeRelativeWeight(localRetainer, block.chainid, endTime); - - totalReturnAmount += stakingPoint.stakingAmount * stakingWeight; - } - totalReturnAmount /= 1e18; - } - /// @dev Claims incentives for the owner of components / agents. /// @notice `msg.sender` must be the owner of components / agents they are passing, otherwise the function will revert. /// @notice If not all `unitIds` belonging to `msg.sender` were provided, they will be untouched and keep accumulating. @@ -713,6 +725,7 @@ contract Dispenser { } _locked = 2; + // Check for the paused state Pause currentPause = paused; if (currentPause == Pause.DevIncentivesPaused || currentPause == Pause.AllPaused) { revert Paused(); @@ -848,6 +861,7 @@ contract Dispenser { revert Overflow(numClaimedEpochs, maxNumClaimingEpochs); } + // Check for the paused state Pause currentPause = paused; if (currentPause == Pause.StakingIncentivesPaused || currentPause == Pause.AllPaused) { revert Paused(); @@ -1017,37 +1031,37 @@ contract Dispenser { _locked = 1; } - /// @dev Sets deposit processor contracts addresses and L2 chain Ids. - /// @notice It is the contract owner responsibility to set correct L1 deposit processor contracts - /// and corresponding supported L2 chain Ids. - /// @param depositProcessors Set of deposit processor contract addresses on L1. - /// @param chainIds Set of corresponding L2 chain Ids. - function setDepositProcessorChainIds( - address[] memory depositProcessors, - uint256[] memory chainIds - ) external { - // Check for the ownership - if (msg.sender != owner) { - revert OwnerOnly(msg.sender, owner); - } + /// @dev Retains staking incentives according to the retainer address to return it back to the staking inflation. + function retain() external { + // Go over epochs and retain funds to return back to the tokenomics + bytes32 localRetainer = retainer; - // Check for array correctness - if (depositProcessors.length != chainIds.length) { - revert WrongArrayLength(depositProcessors.length, chainIds.length); + // Check for zero retainer address + if (localRetainer == 0) { + revert ZeroAddress(); } - // Link L1 and L2 bridge mediators, set L2 chain Ids - for (uint256 i = 0; i < chainIds.length; ++i) { - // Check supported chain Ids on L2 - if (chainIds[i] == 0) { - revert ZeroValue(); - } + (uint256 firstClaimedEpoch, uint256 lastClaimedEpoch) = + _checkpointNomineeAndGetClaimedEpochCounters(localRetainer, block.chainid, maxNumClaimingEpochs); - // Note: depositProcessors[i] might be zero if there is a need to stop processing a specific L2 chain Id - mapChainIdDepositProcessors[chainIds[i]] = depositProcessors[i]; - } + uint256 totalReturnAmount; - emit SetDepositProcessorChainIds(depositProcessors, chainIds); + // Traverse all the claimed epochs + for (uint256 j = firstClaimedEpoch; j < lastClaimedEpoch; ++j) { + // Get service staking info + ITokenomics.StakingPoint memory stakingPoint = + ITokenomics(tokenomics).mapEpochStakingPoints(j); + + // Get epoch end time + uint256 endTime = ITokenomics(tokenomics).getEpochEndTime(j); + + // Get the staking weight for each epoch + (uint256 stakingWeight, ) = + IVoteWeighting(voteWeighting).nomineeRelativeWeight(localRetainer, block.chainid, endTime); + + totalReturnAmount += stakingPoint.stakingAmount * stakingWeight; + } + totalReturnAmount /= 1e18; } /// @dev Syncs the withheld amount according to the data received from L2. @@ -1104,7 +1118,7 @@ contract Dispenser { /// @dev Sets the pause state. /// @param pauseState Pause state. - function setPause(Pause pauseState) external { + function setPauseState(Pause pauseState) external { // Check the contract ownership if (msg.sender != owner) { revert OwnerOnly(msg.sender, owner); diff --git a/contracts/test/MockVoteWeighting.sol b/contracts/test/MockVoteWeighting.sol new file mode 100644 index 00000000..21bbf674 --- /dev/null +++ b/contracts/test/MockVoteWeighting.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +interface IDispenser { + function addNominee(bytes32 nomineeHash) external; + function removeNominee(bytes32 nomineeHash) external; +} + +// Nominee struct +struct Nominee { + bytes32 account; + uint256 chainId; +} + +/// @dev Mocking contract of vote weighting. +contract MockVoteWeighting { + address public immutable dispenser; + uint256 public totalWeight; + + // Set of Nominee structs + Nominee[] public setNominees; + // Mapping of hash(Nominee struct) => nominee Id + mapping(bytes32 => uint256) public mapNomineeIds; + // Mapping of hash(Nominee struct) => nominee weight + mapping(bytes32 => uint256) public mapNomineeRelativeWeights; + + constructor(address _dispenser) { + dispenser = _dispenser; + setNominees.push(Nominee(0, 0)); + } + + /// @dev Checkpoint to fill data for both a specific nominee and common for all nominees. + /// @param account Address of the nominee. + /// @param chainId Chain Id. + function checkpointNominee(bytes32 account, uint256 chainId) external { + + } + + /// @dev Set staking weight. + function setNomineeRelativeWeight(address account, uint256 chainId, uint256 weight) external { + Nominee memory nominee = Nominee(bytes32(uint256(uint160(account))), chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + mapNomineeRelativeWeights[nomineeHash] = weight * 10**18; + + totalWeight += weight; + } + + /// @dev Get Nominee relative weight (not more than 1.0) normalized to 1e18 and the sum of weights. + /// (e.g. 1.0 == 1e18). Inflation which will be received by it is + /// inflation_rate * relativeWeight / 1e18. + /// @param account Address of the nominee in bytes32 form. + /// @param chainId Chain Id. + /// @param time Relative weight at the specified timestamp in the past or present. + /// @return Value of relative weight normalized to 1e18. + /// @return Sum of nominee weights. + function nomineeRelativeWeight(bytes32 account, uint256 chainId, uint256 time) external view returns (uint256, uint256) { + Nominee memory nominee = Nominee(account, chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + return (mapNomineeRelativeWeights[nomineeHash], totalWeight); + } + + /// @dev Records nominee starting epoch number. + function addNominee(address account, uint256 chainId) external { + Nominee memory nominee = Nominee(bytes32(uint256(uint160(account))), chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + + uint256 id = setNominees.length; + mapNomineeIds[nomineeHash] = id; + // Push the nominee into the list + setNominees.push(nominee); + + IDispenser(dispenser).addNominee(nomineeHash); + } + + /// @dev Records nominee removal epoch number. + function removeNominee(address account, uint256 chainId) external { + Nominee memory nominee = Nominee(bytes32(uint256(uint160(account))), chainId); + bytes32 nomineeHash = keccak256(abi.encode(nominee)); + IDispenser(dispenser).removeNominee(nomineeHash); + } +} diff --git a/hardhat.config.js b/hardhat.config.js index 011aa7ef..5648f0d2 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -236,6 +236,6 @@ module.exports = { ] }, gasReporter: { - enabled: true + enabled: false } }; diff --git a/test/DispenserDevIncentives.js b/test/DispenserDevIncentives.js index 4daadb6f..00b203c9 100644 --- a/test/DispenserDevIncentives.js +++ b/test/DispenserDevIncentives.js @@ -3,7 +3,7 @@ const { ethers } = require("hardhat"); const { expect } = require("chai"); const helpers = require("@nomicfoundation/hardhat-network-helpers"); -describe("DispenserStakingIncentives", async () => { +describe("DispenserDevIncentives", async () => { const initialMint = "1" + "0".repeat(26); const AddressZero = "0x" + "0".repeat(40); const oneMonth = 86400 * 30; @@ -15,7 +15,6 @@ describe("DispenserStakingIncentives", async () => { let treasury; let dispenser; let ve; - let voteWeighting; let serviceRegistry; let componentRegistry; let agentRegistry; @@ -49,7 +48,7 @@ describe("DispenserStakingIncentives", async () => { await ve.deployed(); const Dispenser = await ethers.getContractFactory("Dispenser"); - dispenser = await Dispenser.deploy(deployer.address, deployer.address, deployer.address); + dispenser = await Dispenser.deploy(olas.address, deployer.address, deployer.address, deployer.address); await dispenser.deployed(); const Treasury = await ethers.getContractFactory("Treasury"); @@ -135,13 +134,16 @@ describe("DispenserStakingIncentives", async () => { it("Should fail if deploying a dispenser with a zero address", async function () { const Dispenser = await ethers.getContractFactory("Dispenser"); await expect( - Dispenser.deploy(AddressZero, AddressZero, AddressZero) + Dispenser.deploy(AddressZero, AddressZero, AddressZero, AddressZero) ).to.be.revertedWithCustomError(dispenser, "ZeroAddress"); await expect( - Dispenser.deploy(AddressZero, deployer.address, AddressZero) + Dispenser.deploy(deployer.address, AddressZero, AddressZero, AddressZero) ).to.be.revertedWithCustomError(dispenser, "ZeroAddress"); await expect( - Dispenser.deploy(deployer.address, AddressZero, AddressZero) + Dispenser.deploy(deployer.address, deployer.address, AddressZero, AddressZero) + ).to.be.revertedWithCustomError(dispenser, "ZeroAddress"); + await expect( + Dispenser.deploy(deployer.address, deployer.address, deployer.address, AddressZero) ).to.be.revertedWithCustomError(dispenser, "ZeroAddress"); }); }); diff --git a/test/DispenserStakingIncentives.js b/test/DispenserStakingIncentives.js new file mode 100644 index 00000000..c0ce884f --- /dev/null +++ b/test/DispenserStakingIncentives.js @@ -0,0 +1,239 @@ +/*global describe, beforeEach, it, context*/ +const { ethers } = require("hardhat"); +const { expect } = require("chai"); +const helpers = require("@nomicfoundation/hardhat-network-helpers"); + +describe("DispenserStakingIncentives", async () => { + const initialMint = "1" + "0".repeat(26); + const AddressZero = ethers.constants.AddressZero; + const HashZero = ethers.constants.HashZero; + const oneMonth = 86400 * 30; + const chainId = 31337; + const defaultWeight = 1000; + const numClaimedEpochs = 1; + const bridgingDecimals = 18; + const bridgePayload = "0x"; + const epochLen = oneMonth; + const delta = 100; + + let signers; + let deployer; + let olas; + let stakingInstance; + let stakingProxyFactory; + let tokenomics; + let treasury; + let dispenser; + let vw; + let ethereumDepositProcessor; + + function convertAddressToBytes32(account) { + return ("0x" + "0".repeat(24) + account.slice(2)).toLowerCase(); + } + + function convertBytes32ToAddress(account) { + return "0x" + account.slice(26); + } + + // These should not be in beforeEach. + beforeEach(async () => { + signers = await ethers.getSigners(); + deployer = signers[0]; + // Note: this is not a real OLAS token, just an ERC20 mock-up + const olasFactory = await ethers.getContractFactory("ERC20Token"); + olas = await olasFactory.deploy(); + await olas.deployed(); + + const MockStakingProxy = await ethers.getContractFactory("MockStakingProxy"); + stakingInstance = await MockStakingProxy.deploy(olas.address); + await stakingInstance.deployed(); + + const MockStakingFactory = await ethers.getContractFactory("MockStakingFactory"); + stakingProxyFactory = await MockStakingFactory.deploy(); + await stakingProxyFactory.deployed(); + + // Add a default implementation mocked as a proxy address itself + await stakingProxyFactory.addImplementation(stakingInstance.address, stakingInstance.address); + + const Dispenser = await ethers.getContractFactory("Dispenser"); + dispenser = await Dispenser.deploy(olas.address, deployer.address, deployer.address, deployer.address); + await dispenser.deployed(); + + // Vote Weighting mock + const VoteWeighting = await ethers.getContractFactory("MockVoteWeighting"); + vw = await VoteWeighting.deploy(dispenser.address); + await vw.deployed(); + + const Treasury = await ethers.getContractFactory("Treasury"); + treasury = await Treasury.deploy(olas.address, deployer.address, deployer.address, dispenser.address); + await treasury.deployed(); + + // Update for the correct treasury contract + await dispenser.changeManagers(AddressZero, treasury.address, AddressZero); + + const tokenomicsFactory = await ethers.getContractFactory("Tokenomics"); + // Deploy master tokenomics contract + const tokenomicsMaster = await tokenomicsFactory.deploy(); + await tokenomicsMaster.deployed(); + + const proxyData = tokenomicsMaster.interface.encodeFunctionData("initializeTokenomics", + [olas.address, treasury.address, deployer.address, dispenser.address, deployer.address, epochLen, + deployer.address, deployer.address, deployer.address, AddressZero]); + // Deploy tokenomics proxy based on the needed tokenomics initialization + const TokenomicsProxy = await ethers.getContractFactory("TokenomicsProxy"); + const tokenomicsProxy = await TokenomicsProxy.deploy(tokenomicsMaster.address, proxyData); + await tokenomicsProxy.deployed(); + + // Get the tokenomics proxy contract + tokenomics = await ethers.getContractAt("Tokenomics", tokenomicsProxy.address); + + // Change the tokenomics and treasury addresses in the dispenser to correct ones + await dispenser.changeManagers(tokenomics.address, treasury.address, vw.address); + + // Update tokenomics address in treasury + await treasury.changeManagers(tokenomics.address, AddressZero, AddressZero); + + // Mint the initial balance + await olas.mint(deployer.address, initialMint); + + // Give treasury the minter role + await olas.changeMinter(treasury.address); + + // Default Deposit Processor + const EthereumDepositProcessor = await ethers.getContractFactory("EthereumDepositProcessor"); + ethereumDepositProcessor = await EthereumDepositProcessor.deploy(olas.address, dispenser.address, + stakingProxyFactory.address); + await ethereumDepositProcessor.deployed(); + + // Whitelist a default deposit processor + await dispenser.setDepositProcessorChainIds([ethereumDepositProcessor.address], [chainId]); + }); + + context("Initialization", async function () { + it("Should fail when trying to add and remove nominees not by the Vote Weighting contract", async () => { + await expect( + dispenser.connect(deployer).addNominee(HashZero) + ).to.be.revertedWithCustomError(dispenser, "ManagerOnly"); + + await expect( + dispenser.connect(deployer).removeNominee(HashZero) + ).to.be.revertedWithCustomError(dispenser, "ManagerOnly"); + }); + + it("Changing staking parameters", async () => { + // Should fail when not called by the owner + await expect( + dispenser.connect(signers[1]).changeStakingParams(0, 0) + ).to.be.revertedWithCustomError(dispenser, "OwnerOnly"); + + // Set arbitrary parameters + await dispenser.changeStakingParams(10, 10); + + // No action when trying to set parameters to zero + await dispenser.changeStakingParams(0, 0); + expect(await dispenser.maxNumClaimingEpochs()).to.equal(10); + expect(await dispenser.maxNumStakingTargets()).to.equal(10); + }); + + it("Changing retainer from a zero initial address", async () => { + // Should fail when not called by the owner + await expect( + dispenser.connect(signers[1]).changeRetainer(HashZero) + ).to.be.revertedWithCustomError(dispenser, "OwnerOnly"); + + // Trying to set a zero address retainer + await expect( + dispenser.changeRetainer(HashZero) + ).to.be.revertedWithCustomError(dispenser, "ZeroAddress"); + + // Trying to set a retainer address that is not added as a nominee + await expect( + dispenser.changeRetainer(convertAddressToBytes32(signers[1].address)) + ).to.be.revertedWithCustomError(dispenser, "ZeroValue"); + + // Trying to add retainer as a nominee when the contract is paused + await expect( + vw.addNominee(signers[1].address, chainId) + ).to.be.revertedWithCustomError(dispenser, "Paused"); + + // Try to unpause the dispenser not by the owner + await expect( + dispenser.connect(signers[1]).setPauseState(0) + ).to.be.revertedWithCustomError(dispenser, "OwnerOnly"); + + // Unpause the dispenser + await dispenser.setPauseState(0); + + // Add retainer as a nominee + await vw.addNominee(signers[1].address, chainId); + + // Change the retainer + await dispenser.changeRetainer(convertAddressToBytes32(signers[1].address)); + }); + + it("Should fail when setting deposit processors and chain Ids with incorrect parameters", async () => { + // Should fail when not called by the owner + await expect( + dispenser.connect(signers[1]).setDepositProcessorChainIds([],[]) + ).to.be.revertedWithCustomError(dispenser, "OwnerOnly"); + + // Should fail when array lengths are zero + await expect( + dispenser.setDepositProcessorChainIds([],[]) + ).to.be.revertedWithCustomError(dispenser, "WrongArrayLength"); + + // Should fail when array lengths do not match + await expect( + dispenser.setDepositProcessorChainIds([ethereumDepositProcessor.address],[]) + ).to.be.revertedWithCustomError(dispenser, "WrongArrayLength"); + + // Should fail when chain Id is zero + await expect( + dispenser.setDepositProcessorChainIds([ethereumDepositProcessor.address],[0]) + ).to.be.revertedWithCustomError(dispenser, "ZeroValue"); + }); + }); + + context("Staking incentives", async function () { + it("Claim staking incentives for a single nominee", async () => { + // Take a snapshot of the current state of the blockchain + const snapshot = await helpers.takeSnapshot(); + + // Set staking fraction to 100% + await tokenomics.changeIncentiveFractions(0, 0, 0, 0, 0, 100); + // Changing staking parameters + await tokenomics.changeStakingParams(50, 10); + + // Checkpoint to apply changes + await helpers.time.increase(epochLen); + await tokenomics.checkpoint(); + + // Unpause the dispenser + await dispenser.setPauseState(0); + + // Add a staking instance as a nominee + await vw.addNominee(stakingInstance.address, chainId); + + // Vote for the nominee + await vw.setNomineeRelativeWeight(stakingInstance.address, chainId, defaultWeight); + + // Checkpoint to apply changes + await helpers.time.increase(epochLen); + await tokenomics.checkpoint(); + + const stakingTarget = convertAddressToBytes32(stakingInstance.address); + // Calculate staking incentives + const stakingAmounts = await dispenser.callStatic.calculateStakingIncentives(numClaimedEpochs, chainId, + stakingTarget, bridgingDecimals); + // We deliberately setup the voting such that there is staking amount and return amount + expect(stakingAmounts.totalStakingAmount).to.gt(0); + expect(stakingAmounts.totalReturnAmount).to.gt(0); + + // Claim staking incentives + await dispenser.claimStakingIncentives(numClaimedEpochs, chainId, stakingTarget, bridgePayload); + + // Restore to the state of the snapshot + await snapshot.restore(); + }); + }); +}); diff --git a/test/StakingBridging.js b/test/StakingBridging.js index d45a343d..dc61150b 100644 --- a/test/StakingBridging.js +++ b/test/StakingBridging.js @@ -52,8 +52,7 @@ describe("StakingBridging", async () => { await stakingProxyFactory.deployed(); // Add a default implementation mocked as a proxy address itself - await stakingProxyFactory.addImplementation(stakingInstance.address, - stakingInstance.address); + await stakingProxyFactory.addImplementation(stakingInstance.address, stakingInstance.address); const MockStakingDispenser = await ethers.getContractFactory("MockStakingDispenser"); dispenser = await MockStakingDispenser.deploy(olas.address);