diff --git a/contracts/Distribution.sol b/contracts/Distribution.sol index 0b5708c..ed18d59 100644 --- a/contracts/Distribution.sol +++ b/contracts/Distribution.sol @@ -46,6 +46,11 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { /**********************************************************************************************/ /*** Init ***/ /**********************************************************************************************/ + + constructor() { + _disableInitializers(); + } + function Distribution_init( address depositToken_, address l1Sender_, @@ -109,9 +114,7 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { } function _validatePool(Pool calldata pool_) private pure { - if (pool_.rewardDecrease > 0) { - require(pool_.decreaseInterval > 0, "DS: invalid reward decrease"); - } + require(pool_.decreaseInterval > 0, "DS: invalid decrease interval"); } /**********************************************************************************************/ diff --git a/contracts/L1Sender.sol b/contracts/L1Sender.sol index deea6cd..ab04abc 100644 --- a/contracts/L1Sender.sol +++ b/contracts/L1Sender.sol @@ -20,6 +20,15 @@ contract L1Sender is IL1Sender, ERC165, OwnableUpgradeable, UUPSUpgradeable { DepositTokenConfig public depositTokenConfig; RewardTokenConfig public rewardTokenConfig; + modifier onlyDistribution() { + require(_msgSender() == distribution, "L1S: invalid sender"); + _; + } + + constructor() { + _disableInitializers(); + } + function L1Sender__init( address distribution_, RewardTokenConfig calldata rewardTokenConfig_, @@ -91,7 +100,7 @@ contract L1Sender is IL1Sender, ERC165, OwnableUpgradeable, UUPSUpgradeable { uint256 gasLimit_, uint256 maxFeePerGas_, uint256 maxSubmissionCost_ - ) external payable returns (bytes memory) { + ) external payable onlyDistribution returns (bytes memory) { DepositTokenConfig storage config = depositTokenConfig; // Get current stETH balance @@ -112,9 +121,7 @@ contract L1Sender is IL1Sender, ERC165, OwnableUpgradeable, UUPSUpgradeable { ); } - function sendMintMessage(address user_, uint256 amount_, address refundTo_) external payable { - require(_msgSender() == distribution, "L1S: invalid sender"); - + function sendMintMessage(address user_, uint256 amount_, address refundTo_) external payable onlyDistribution { RewardTokenConfig storage config = rewardTokenConfig; bytes memory receiverAndSenderAddresses_ = abi.encodePacked(config.receiver, address(this)); diff --git a/contracts/L2MessageReceiver.sol b/contracts/L2MessageReceiver.sol index ce5c070..4649d2a 100644 --- a/contracts/L2MessageReceiver.sol +++ b/contracts/L2MessageReceiver.sol @@ -4,19 +4,20 @@ pragma solidity ^0.8.20; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {ILayerZeroReceiver} from "@layerzerolabs/lz-evm-sdk-v1-0.7/contracts/interfaces/ILayerZeroReceiver.sol"; - import {IMOR} from "./interfaces/IMOR.sol"; import {IL2MessageReceiver} from "./interfaces/IL2MessageReceiver.sol"; -contract L2MessageReceiver is ILayerZeroReceiver, IL2MessageReceiver, OwnableUpgradeable, UUPSUpgradeable { +contract L2MessageReceiver is IL2MessageReceiver, OwnableUpgradeable, UUPSUpgradeable { address public rewardToken; Config public config; - mapping(uint16 => mapping(uint64 => bool)) public isNonceUsed; mapping(uint16 => mapping(bytes => mapping(uint64 => bytes32))) public failedMessages; + constructor() { + _disableInitializers(); + } + function L2MessageReceiver__init() external initializer { __Ownable_init(); __UUPSUpgradeable_init(); @@ -41,12 +42,11 @@ contract L2MessageReceiver is ILayerZeroReceiver, IL2MessageReceiver, OwnableUpg function nonblockingLzReceive( uint16 senderChainId_, bytes memory senderAndReceiverAddresses_, - uint64 nonce_, bytes memory payload_ ) public { require(_msgSender() == address(this), "L2MR: invalid caller"); - _nonblockingLzReceive(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_); + _nonblockingLzReceive(senderChainId_, senderAndReceiverAddresses_, payload_); } function retryMessage( @@ -59,7 +59,7 @@ contract L2MessageReceiver is ILayerZeroReceiver, IL2MessageReceiver, OwnableUpg require(payloadHash_ != bytes32(0), "L2MR: no stored message"); require(keccak256(payload_) == payloadHash_, "L2MR: invalid payload"); - _nonblockingLzReceive(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_); + _nonblockingLzReceive(senderChainId_, senderAndReceiverAddresses_, payload_); delete failedMessages[senderChainId_][senderAndReceiverAddresses_][nonce_]; @@ -76,7 +76,6 @@ contract L2MessageReceiver is ILayerZeroReceiver, IL2MessageReceiver, OwnableUpg IL2MessageReceiver(address(this)).nonblockingLzReceive( senderChainId_, senderAndReceiverAddresses_, - nonce_, payload_ ) { @@ -91,10 +90,8 @@ contract L2MessageReceiver is ILayerZeroReceiver, IL2MessageReceiver, OwnableUpg function _nonblockingLzReceive( uint16 senderChainId_, bytes memory senderAndReceiverAddresses_, - uint64 nonce_, bytes memory payload_ ) private { - require(!isNonceUsed[senderChainId_][nonce_], "L2MR: invalid nonce"); require(senderChainId_ == config.senderChainId, "L2MR: invalid sender chain ID"); address sender_; @@ -106,8 +103,6 @@ contract L2MessageReceiver is ILayerZeroReceiver, IL2MessageReceiver, OwnableUpg (address user_, uint256 amount_) = abi.decode(payload_, (address, uint256)); IMOR(rewardToken).mint(user_, amount_); - - isNonceUsed[senderChainId_][nonce_] = true; } function _authorizeUpgrade(address) internal view override onlyOwner {} diff --git a/contracts/L2TokenReceiver.sol b/contracts/L2TokenReceiver.sol index 3ad6bff..44635ae 100644 --- a/contracts/L2TokenReceiver.sol +++ b/contracts/L2TokenReceiver.sol @@ -16,6 +16,10 @@ contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeabl SwapParams public params; + constructor() { + _disableInitializers(); + } + function L2TokenReceiver__init( address router_, address nonfungiblePositionManager_, diff --git a/contracts/interfaces/IDistribution.sol b/contracts/interfaces/IDistribution.sol index cb16a23..7fd0bc6 100644 --- a/contracts/interfaces/IDistribution.sol +++ b/contracts/interfaces/IDistribution.sol @@ -209,7 +209,7 @@ interface IDistribution { function l1Sender() external view returns (address); /** - * The function to get the amount of deposit tokens that are staked in the pool. + * The function to get the amount of deposit tokens that are staked in all of the public pools. * @dev The value accumulates the amount amount despite the rate differences. * @return The amount of deposit tokens. */ diff --git a/contracts/interfaces/IL2MessageReceiver.sol b/contracts/interfaces/IL2MessageReceiver.sol index 3dd3668..7aaece2 100644 --- a/contracts/interfaces/IL2MessageReceiver.sol +++ b/contracts/interfaces/IL2MessageReceiver.sol @@ -64,16 +64,14 @@ interface IL2MessageReceiver is ILayerZeroReceiver { function setParams(address rewardToken_, Config calldata config_) external; /** - * LayerZero endpoint call this function to check a transaction capabilities. + * The function to call the nonblockingLzReceive. * @param senderChainId_ The source endpoint identifier. * @param senderAndReceiverAddresses_ The source sending contract address from the source chain. - * @param nonce_ The ordered message nonce. * @param payload_ The signed payload is the UA bytes has encoded to be sent. */ function nonblockingLzReceive( uint16 senderChainId_, bytes memory senderAndReceiverAddresses_, - uint64 nonce_, bytes memory payload_ ) external; diff --git a/contracts/libs/LinearDistributionIntervalDecrease.sol b/contracts/libs/LinearDistributionIntervalDecrease.sol index 9e559d2..cc27f88 100644 --- a/contracts/libs/LinearDistributionIntervalDecrease.sol +++ b/contracts/libs/LinearDistributionIntervalDecrease.sol @@ -142,7 +142,10 @@ library LinearDistributionIntervalDecrease { uint256 decreaseRewardAmount_ = intervalsPassedBefore_ * decreaseAmount_; - // Overflow impossible because 'endTime_' can't be more then 'maxEndTime_' + if (decreaseRewardAmount_ >= initialAmount_) { + return 0; + } + uint256 initialReward_ = initialAmount_ - decreaseRewardAmount_; // END diff --git a/test/Distribution.test.ts b/test/Distribution.test.ts index d063234..a08b6f6 100644 --- a/test/Distribution.test.ts +++ b/test/Distribution.test.ts @@ -189,6 +189,16 @@ describe('Distribution', () => { afterEach(reverter.revert); describe('UUPS proxy functionality', () => { + describe('#constructor', () => { + it('should disable initialize function', async () => { + const reason = 'Initializable: contract is already initialized'; + + const distribution = await distributionFactory.deploy(); + + await expect(distribution.Distribution_init(depositToken, l1Sender, [])).to.be.revertedWith(reason); + }); + }); + describe('#Distribution_init', () => { it('should set correct data after creation', async () => { const depositToken_ = await distribution.depositToken(); @@ -204,7 +214,12 @@ describe('Distribution', () => { decreaseInterval: oneDay * 2, }; - const distribution = await distributionFactory.deploy(); + const distributionProxy = await ( + await ethers.getContractFactory('ERC1967Proxy') + ).deploy(await distributionFactory.deploy(), '0x'); + + const distribution = distributionFactory.attach(await distributionProxy.getAddress()) as Distribution; + await distribution.Distribution_init(depositToken, l1Sender, [pool1, pool2]); const pool1Data: IDistribution.PoolStruct = await distribution.pools(0); @@ -275,11 +290,11 @@ describe('Distribution', () => { await expect(distribution.createPool(pool)).to.be.rejectedWith('DS: invalid payout start value'); }); - it('if `rewardDecrease > 0 && decreaseInterval == 0`', async () => { + it('if `decreaseInterval == 0`', async () => { const pool = getDefaultPool(); pool.decreaseInterval = 0; - await expect(distribution.createPool(pool)).to.be.rejectedWith('DS: invalid reward decrease'); + await expect(distribution.createPool(pool)).to.be.rejectedWith('DS: invalid decrease interval'); }); }); @@ -328,10 +343,10 @@ describe('Distribution', () => { }); describe('should revert if try to edit pool with incorrect data', () => { - it('if `rewardDecrease > 0 && decreaseInterval == 0`', async () => { + it('if `decreaseInterval == 0`', async () => { const newPool = { ...defaultPool, decreaseInterval: 0 }; - await expect(distribution.editPool(poolId, newPool)).to.be.rejectedWith('DS: invalid reward decrease'); + await expect(distribution.editPool(poolId, newPool)).to.be.rejectedWith('DS: invalid decrease interval'); }); }); diff --git a/test/L1Sender.test.ts b/test/L1Sender.test.ts index b6d7932..b5e08ab 100644 --- a/test/L1Sender.test.ts +++ b/test/L1Sender.test.ts @@ -120,20 +120,35 @@ describe('L1Sender', () => { }); describe('UUPS proxy functionality', () => { - describe('#Distribution_init', () => { - it('should revert if try to call init function twice', async () => { + let rewardTokenConfig: IL1Sender.RewardTokenConfigStruct; + let depositTokenConfig: IL1Sender.DepositTokenConfigStruct; + + before(async () => { + rewardTokenConfig = { + gateway: lZEndpointMockL1, + receiver: l2MessageReceiver, + receiverChainId: receiverChainId, + }; + depositTokenConfig = { + token: depositToken, + gateway: gatewayRouter, + receiver: SECOND, + }; + }); + + describe('#constructor', () => { + it('should disable initialize function', async () => { const reason = 'Initializable: contract is already initialized'; - const rewardTokenConfig: IL1Sender.RewardTokenConfigStruct = { - gateway: lZEndpointMockL1, - receiver: l2MessageReceiver, - receiverChainId: receiverChainId, - }; - const depositTokenConfig: IL1Sender.DepositTokenConfigStruct = { - token: depositToken, - gateway: gatewayRouter, - receiver: SECOND, - }; + const l1Sender = await (await ethers.getContractFactory('L1Sender')).deploy(); + + await expect(l1Sender.L1Sender__init(OWNER, rewardTokenConfig, depositTokenConfig)).to.be.rejectedWith(reason); + }); + }); + + describe('#L1Sender__init', () => { + it('should revert if try to call init function twice', async () => { + const reason = 'Initializable: contract is already initialized'; await expect(l1Sender.L1Sender__init(OWNER, rewardTokenConfig, depositTokenConfig)).to.be.rejectedWith(reason); }); @@ -348,6 +363,9 @@ describe('L1Sender', () => { expect(await depositToken.balanceOf(SECOND)).to.eq('100'); }); + it('should revert if not called by the owner', async () => { + await expect(l1Sender.connect(SECOND).sendDepositToken(1, 1, 1)).to.be.revertedWith('L1S: invalid sender'); + }); }); describe('sendMintMessage', () => { diff --git a/test/L2MessageReceiver.test.ts b/test/L2MessageReceiver.test.ts index b4f87e0..315fc95 100644 --- a/test/L2MessageReceiver.test.ts +++ b/test/L2MessageReceiver.test.ts @@ -48,7 +48,17 @@ describe('L2MessageReceiver', () => { }); describe('UUPS proxy functionality', () => { - describe('#Distribution_init', () => { + describe('#constructor', () => { + it('should disable initialize function', async () => { + const reason = 'Initializable: contract is already initialized'; + + const l2MessageReceiver = await (await ethers.getContractFactory('L2MessageReceiver')).deploy(); + + await expect(l2MessageReceiver.L2MessageReceiver__init()).to.be.rejectedWith(reason); + }); + }); + + describe('#L2MessageReceiver__init', () => { it('should revert if try to call init function twice', async () => { const reason = 'Initializable: contract is already initialized'; @@ -99,7 +109,7 @@ describe('L2MessageReceiver', () => { }); describe('#lzReceive', () => { - it('should update nonce and mint tokens', async () => { + it('should mint tokens', async () => { const address = ethers.solidityPacked( ['address', 'address'], [await OWNER.getAddress(), await l2MessageReceiver.getAddress()], @@ -110,40 +120,51 @@ describe('L2MessageReceiver', () => { ); const tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); await expect(tx).to.changeTokenBalance(mor, SECOND, wei(1)); - expect(await l2MessageReceiver.isNonceUsed(2, 5)).to.be.equal(true); }); - it('should update nonce and mint tokens', async () => { + it('should mint tokens', async () => { const address = ethers.solidityPacked( ['address', 'address'], [await OWNER.getAddress(), await l2MessageReceiver.getAddress()], ); let payload = ethers.AbiCoder.defaultAbiCoder().encode( ['address', 'uint256'], - [await SECOND.getAddress(), wei(99)], + [await SECOND.getAddress(), wei(95)], ); let tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); - await expect(tx).to.changeTokenBalance(mor, SECOND, wei(99)); - expect(await l2MessageReceiver.isNonceUsed(2, 5)).to.be.equal(true); + await expect(tx).to.changeTokenBalance(mor, SECOND, wei(95)); payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [await SECOND.getAddress(), wei(2)]); tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 6, payload); + await expect(tx).to.changeTokenBalance(mor, SECOND, wei(2)); + payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [await SECOND.getAddress(), wei(5)]); + tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 7, payload); + await expect(tx).to.changeTokenBalance(mor, SECOND, 0); + expect(await l2MessageReceiver.failedMessages(2, address, 7)).to.eq(ethers.keccak256(payload)); }); it('should revert if provided wrong lzEndpoint', async () => { await expect(l2MessageReceiver.lzReceive(0, '0x', 1, '0x')).to.be.revertedWith('L2MR: invalid gateway'); }); - it('should fail if provided wrong nonce', async () => { + it('should fail if provided wrong mint amount', async () => { const address = ethers.solidityPacked( ['address', 'address'], [await OWNER.getAddress(), await l2MessageReceiver.getAddress()], ); const payload = ethers.AbiCoder.defaultAbiCoder().encode( ['address', 'uint256'], - [await SECOND.getAddress(), wei(99)], + [await SECOND.getAddress(), wei(100)], ); - await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); + + let tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); + await expect(tx).to.changeTokenBalance(mor, SECOND, wei(100)); expect(await l2MessageReceiver.failedMessages(2, address, 5)).to.eq(ZERO_BYTES32); await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); expect(await l2MessageReceiver.failedMessages(2, address, 5)).to.eq(ethers.keccak256(payload)); + + await mor.connect(SECOND).burn(wei(100)); + + tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 6, payload); + expect(await l2MessageReceiver.failedMessages(2, address, 6)).to.eq(ZERO_BYTES32); + await expect(tx).to.changeTokenBalance(mor, SECOND, wei(100)); }); it('should fail if provided wrong mint amount', async () => { const address = ethers.solidityPacked( @@ -169,9 +190,7 @@ describe('L2MessageReceiver', () => { describe('#nonblockingLzReceive', () => { it('should revert if invalid caller', async () => { - await expect(l2MessageReceiver.nonblockingLzReceive(2, '0x', 999, '0x')).to.be.revertedWith( - 'L2MR: invalid caller', - ); + await expect(l2MessageReceiver.nonblockingLzReceive(2, '0x', '0x')).to.be.revertedWith('L2MR: invalid caller'); }); }); @@ -208,7 +227,7 @@ describe('L2MessageReceiver', () => { expect(await l2MessageReceiver.failedMessages(chainId, senderAndReceiverAddresses, 999)).to.eq(ZERO_BYTES32); }); it('should revert if invalid caller', async () => { - await expect(l2MessageReceiver.nonblockingLzReceive(chainId, '0x', 999, '0x')).to.be.revertedWith( + await expect(l2MessageReceiver.nonblockingLzReceive(chainId, '0x', '0x')).to.be.revertedWith( 'L2MR: invalid caller', ); }); diff --git a/test/L2TokenReceiver.test.ts b/test/L2TokenReceiver.test.ts index aa49583..e7d96d1 100644 --- a/test/L2TokenReceiver.test.ts +++ b/test/L2TokenReceiver.test.ts @@ -71,7 +71,22 @@ describe('L2TokenReceiver', () => { }); describe('UUPS proxy functionality', () => { - describe('#Distribution_init', () => { + it('should disable initialize function', async () => { + const reason = 'Initializable: contract is already initialized'; + + const l2TokenReceiver = await (await ethers.getContractFactory('L2TokenReceiver')).deploy(); + + await expect( + l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, { + tokenIn: inputToken, + tokenOut: outputToken, + fee: 500, + sqrtPriceLimitX96: 0, + }), + ).to.be.rejectedWith(reason); + }); + + describe('#L2TokenReceiver__init', () => { it('should revert if try to call init function twice', async () => { const reason = 'Initializable: contract is already initialized'; diff --git a/test/libs/LinearDistributionIntervalDecrease.test.ts b/test/libs/LinearDistributionIntervalDecrease.test.ts index 60ffb99..e90237b 100644 --- a/test/libs/LinearDistributionIntervalDecrease.test.ts +++ b/test/libs/LinearDistributionIntervalDecrease.test.ts @@ -132,6 +132,9 @@ describe('LinearDistributionIntervalDecrease', () => { reward = await distribution.getPeriodReward(0, payoutStart - 1 * oneHour, payoutStart + 999 * oneHour); expect(reward).to.eq(wei(149)); + + reward = await distribution.getPeriodReward(0, payoutStart + 12 * oneHour, payoutStart + 999 * oneHour); + expect(reward).to.eq(wei(49 / 2)); }); it('should return correct rewards if `rewardDecrease` == 0', async () => { const pool: IDistribution.PoolStruct = {