diff --git a/contracts/test/utils/FlowLimitTestLiveNetwork.sol b/contracts/test/utils/FlowLimitTestLiveNetwork.sol new file mode 100644 index 00000000..d85fda38 --- /dev/null +++ b/contracts/test/utils/FlowLimitTestLiveNetwork.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { IFlowLimit } from '../../interfaces/IFlowLimit.sol'; + +contract FlowLimitTestLiveNetwork is IFlowLimit { + uint256 internal constant FLOW_LIMIT_SLOT = 0x201b7a0b7c19aaddc4ce9579b7df8d2db123805861bc7763627f13e04d8af42f; + uint256 internal constant PREFIX_FLOW_OUT_AMOUNT = uint256(keccak256('flow-out-amount')); + uint256 internal constant PREFIX_FLOW_IN_AMOUNT = uint256(keccak256('flow-in-amount')); + + uint256 internal constant EPOCH_TIME = 60; + + function getFlowLimit() public view returns (uint256 flowLimit) { + assembly { + flowLimit := sload(FLOW_LIMIT_SLOT) + } + } + + function _setFlowLimit(uint256 flowLimit) internal { + assembly { + sstore(FLOW_LIMIT_SLOT, flowLimit) + } + + emit FlowLimitSet(flowLimit); + } + + function _getFlowOutSlot(uint256 epoch) internal pure returns (uint256 slot) { + slot = uint256(keccak256(abi.encode(PREFIX_FLOW_OUT_AMOUNT, epoch))); + } + + function _getFlowInSlot(uint256 epoch) internal pure returns (uint256 slot) { + slot = uint256(keccak256(abi.encode(PREFIX_FLOW_IN_AMOUNT, epoch))); + } + + function getFlowOutAmount() external view returns (uint256 flowOutAmount) { + uint256 epoch = block.timestamp / EPOCH_TIME; + uint256 slot = _getFlowOutSlot(epoch); + + assembly { + flowOutAmount := sload(slot) + } + } + + function getFlowInAmount() external view returns (uint256 flowInAmount) { + uint256 epoch = block.timestamp / EPOCH_TIME; + uint256 slot = _getFlowInSlot(epoch); + + assembly { + flowInAmount := sload(slot) + } + } + + function _addFlow(uint256 flowLimit, uint256 slotToAdd, uint256 slotToCompare, uint256 flowAmount) internal { + uint256 flowToAdd; + uint256 flowToCompare; + + assembly { + flowToAdd := sload(slotToAdd) + flowToCompare := sload(slotToCompare) + } + + if (flowToAdd + flowAmount > flowToCompare + flowLimit) revert FlowLimitExceeded(); + if (flowAmount > flowLimit) revert FlowLimitExceeded(); + + assembly { + sstore(slotToAdd, add(flowToAdd, flowAmount)) + } + } + + function _addFlowOut(uint256 flowOutAmount) internal { + uint256 flowLimit = getFlowLimit(); + if (flowLimit == 0) return; + + uint256 epoch = block.timestamp / EPOCH_TIME; + uint256 slotToAdd = _getFlowOutSlot(epoch); + uint256 slotToCompare = _getFlowInSlot(epoch); + + _addFlow(flowLimit, slotToAdd, slotToCompare, flowOutAmount); + } + + function _addFlowIn(uint256 flowInAmount) internal { + uint256 flowLimit = getFlowLimit(); + if (flowLimit == 0) return; + + uint256 epoch = block.timestamp / EPOCH_TIME; + uint256 slotToAdd = _getFlowInSlot(epoch); + uint256 slotToCompare = _getFlowOutSlot(epoch); + + _addFlow(flowLimit, slotToAdd, slotToCompare, flowInAmount); + } + + function setFlowLimit(uint256 flowLimit) external { + _setFlowLimit(flowLimit); + } + + function addFlowIn(uint256 flowInAmount) external { + _addFlowIn(flowInAmount); + } + + function addFlowOut(uint256 flowOutAmount) external { + _addFlowOut(flowOutAmount); + } +} diff --git a/test/UtilsTest.js b/test/UtilsTest.js index ccb136d9..7fdd61d4 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -8,7 +8,7 @@ const { Wallet, Contract } = ethers; const { AddressZero } = ethers.constants; const { defaultAbiCoder, arrayify, toUtf8Bytes, hexlify } = ethers.utils; const { expect } = chai; -const { getRandomBytes32, expectRevert } = require('./utils'); +const { getRandomBytes32, expectRevert, isHardhat, waitFor } = require('./utils'); const { deployContract } = require('../scripts/deploy'); const ImplemenationTest = require('../artifacts/contracts/test/utils/ImplementationTest.sol/ImplementationTest.json'); @@ -207,18 +207,25 @@ describe('ExpressCallHandler', () => { describe('FlowLimit', async () => { let test; - const flowLimit = 5; + const flowLimit = isHardhat ? 5 : 2; before(async () => { - test = await deployContract(ownerWallet, 'FlowLimitTest'); + test = isHardhat + ? await deployContract(ownerWallet, 'FlowLimitTest') + : await deployContract(ownerWallet, 'FlowLimitTestLiveNetwork'); }); async function nextEpoch() { - const latest = Number(await time.latest()); - const epoch = 6 * 3600; - const next = (Math.floor(latest / epoch) + 1) * epoch; + const epoch = isHardhat ? 6 * 3600 : 60; - await time.increaseTo(next); + if (isHardhat) { + const latest = Number(await time.latest()); + const next = (Math.floor(latest / epoch) + 1) * epoch; + + await time.increaseTo(next); + } else { + await waitFor(epoch); + } } it('Should calculate hardcoded constants correctly', async () => { diff --git a/test/utils.js b/test/utils.js index d9163b9e..84dea506 100644 --- a/test/utils.js +++ b/test/utils.js @@ -10,6 +10,8 @@ const getGasOptions = () => { return network.config.blockGasLimit ? { gasLimit: network.config.blockGasLimit.toString() } : { gasLimit: 5e6 }; // defaults to 5M gas for revert tests to work correctly }; +const isHardhat = network.name === 'hardhat'; + const expectRevert = async (txFunc, contract, error) => { if (network.config.skipRevertTests) { await expect(txFunc(getGasOptions())).to.be.reverted; @@ -18,6 +20,15 @@ const expectRevert = async (txFunc, contract, error) => { } }; +const waitFor = async (timeDelay) => { + if (isHardhat) { + await network.provider.send('evm_increaseTime', [timeDelay]); + await network.provider.send('evm_mine'); + } else { + await new Promise((resolve) => setTimeout(resolve, timeDelay * 1000)); + } +}; + const getChainId = () => { return network.config.chainId; }; @@ -26,5 +37,7 @@ module.exports = { getRandomBytes32, getChainId, getGasOptions, + isHardhat, expectRevert, + waitFor, };