Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit fixes #15

Merged
merged 9 commits into from
Jan 24, 2024
9 changes: 6 additions & 3 deletions contracts/Distribution.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,24 @@
/*** Modifiers ***/
/**********************************************************************************************/
modifier poolExists(uint256 poolId_) {
require(_poolExists(poolId_), "DS: pool doesn't exist");

Check warning on line 37 in contracts/Distribution.sol

View workflow job for this annotation

GitHub Actions / test

Use Custom Errors instead of require statements
_;
}

modifier poolPublic(uint256 poolId_) {
require(pools[poolId_].isPublic, "DS: pool isn't public");

Check warning on line 42 in contracts/Distribution.sol

View workflow job for this annotation

GitHub Actions / test

Use Custom Errors instead of require statements
_;
}

/**********************************************************************************************/
/*** Init ***/
/**********************************************************************************************/

constructor() {
_disableInitializers();
}

function Distribution_init(

Check warning on line 54 in contracts/Distribution.sol

View workflow job for this annotation

GitHub Actions / test

Function name must be in mixedCase
address depositToken_,
address l1Sender_,
Pool[] calldata poolsInfo_
Expand All @@ -66,7 +71,7 @@
/*** Pool managment and data retrieval ***/
/**********************************************************************************************/
function createPool(Pool calldata pool_) public onlyOwner {
require(pool_.payoutStart > block.timestamp, "DS: invalid payout start value");

Check warning on line 74 in contracts/Distribution.sol

View workflow job for this annotation

GitHub Actions / test

Use Custom Errors instead of require statements

_validatePool(pool_);
pools.push(pool_);
Expand All @@ -76,7 +81,7 @@

function editPool(uint256 poolId_, Pool calldata pool_) external onlyOwner poolExists(poolId_) {
_validatePool(pool_);
require(pools[poolId_].isPublic == pool_.isPublic, "DS: invalid pool type");

Check warning on line 84 in contracts/Distribution.sol

View workflow job for this annotation

GitHub Actions / test

Use Custom Errors instead of require statements

PoolData storage poolData = poolsData[poolId_];
uint256 currentPoolRate_ = _getCurrentPoolRate(poolId_);
Expand Down Expand Up @@ -109,9 +114,7 @@
}

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");

Check warning on line 117 in contracts/Distribution.sol

View workflow job for this annotation

GitHub Actions / test

Use Custom Errors instead of require statements
}

/**********************************************************************************************/
Expand All @@ -122,8 +125,8 @@
address[] calldata users_,
uint256[] calldata amounts_
) external onlyOwner poolExists(poolId_) {
require(!pools[poolId_].isPublic, "DS: pool is public");

Check warning on line 128 in contracts/Distribution.sol

View workflow job for this annotation

GitHub Actions / test

Use Custom Errors instead of require statements
require(users_.length == amounts_.length, "DS: invalid length");

Check warning on line 129 in contracts/Distribution.sol

View workflow job for this annotation

GitHub Actions / test

Use Custom Errors instead of require statements

uint256 currentPoolRate_ = _getCurrentPoolRate(poolId_);

Expand Down Expand Up @@ -153,11 +156,11 @@
PoolData storage poolData = poolsData[poolId_];
UserData storage userData = usersData[user_][poolId_];

require(block.timestamp > pool.payoutStart + pool.claimLockPeriod, "DS: pool claim is locked");

Check warning on line 159 in contracts/Distribution.sol

View workflow job for this annotation

GitHub Actions / test

Use Custom Errors instead of require statements

uint256 currentPoolRate_ = _getCurrentPoolRate(poolId_);
uint256 pendingRewards_ = _getCurrentUserReward(currentPoolRate_, userData);
require(pendingRewards_ > 0, "DS: nothing to claim");

Check warning on line 163 in contracts/Distribution.sol

View workflow job for this annotation

GitHub Actions / test

Use Custom Errors instead of require statements

// Update pool data
poolData.lastUpdate = uint128(block.timestamp);
Expand Down
15 changes: 11 additions & 4 deletions contracts/L1Sender.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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_,
Expand Down Expand Up @@ -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
Expand All @@ -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));
Expand Down
19 changes: 7 additions & 12 deletions contracts/L2MessageReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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(
Expand All @@ -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_];

Expand All @@ -76,7 +76,6 @@ contract L2MessageReceiver is ILayerZeroReceiver, IL2MessageReceiver, OwnableUpg
IL2MessageReceiver(address(this)).nonblockingLzReceive(
senderChainId_,
senderAndReceiverAddresses_,
nonce_,
payload_
)
{
Expand All @@ -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_;
Expand All @@ -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 {}
Expand Down
4 changes: 4 additions & 0 deletions contracts/L2TokenReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeabl

SwapParams public params;

constructor() {
_disableInitializers();
}

function L2TokenReceiver__init(
address router_,
address nonfungiblePositionManager_,
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IDistribution.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
4 changes: 1 addition & 3 deletions contracts/interfaces/IL2MessageReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
5 changes: 4 additions & 1 deletion contracts/libs/LinearDistributionIntervalDecrease.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
25 changes: 20 additions & 5 deletions test/Distribution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand Down Expand Up @@ -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');
});
});

Expand Down Expand Up @@ -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');
});
});

Expand Down
42 changes: 30 additions & 12 deletions test/L1Sender.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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', () => {
Expand Down
Loading