-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #71 from valory-xyz/dual_token_testing
refactor: dual token is onwerless, naming correction
- Loading branch information
Showing
5 changed files
with
896 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# Internal audit of autonolas-staking-programmes | ||
The review has been performed based on the contract code in the following repository:<br> | ||
`https://github.com/valory-xyz/autonolas-staking-programmes` <br> | ||
commit: `v1.6.0-pre-internal-audit` or `710ee320d06acd0ee793fa13677987fb347dffdc` <br> | ||
|
||
## Objectives | ||
The audit focused on contracts in this repo. <br> | ||
|
||
### ERC20/ERC721 checks | ||
N/A | ||
|
||
### Security issues. | ||
|
||
#### Notes: DualStakingToken named revert | ||
``` | ||
if (stakerInfo.account != address(0)) { | ||
revert(); | ||
} | ||
if (stakerInfo.account == address(0)) { | ||
revert(); | ||
} | ||
if (stakingState != IStaking.StakingState.Evicted) { | ||
revert(); | ||
} | ||
// Check for staker existence | ||
if (stakerInfo.account == address(0)) { | ||
revert(); | ||
} | ||
``` | ||
[] | ||
|
||
#### Notes: One way deposit(). Should there be an opposite function or would overcomplicate the logic? | ||
``` | ||
function deposit(uint256 amount) external {} <- can't redo deposit() | ||
``` | ||
[] | ||
|
||
#### Notes: reward in both token? triple check and test logic | ||
``` | ||
Why does the code only show rewards in second tokens? Shouldn't it be paid out symmetrically in OLAS/second ERC20 token? | ||
``` | ||
[] | ||
|
||
#### Notes: reward in second token. triple check and test logic | ||
``` | ||
Why do we send a reward equal stakerInfo.reward to the second tokens? Why not stakerInfo.reward * ratio? | ||
uint256 reward = stakerInfo.reward; | ||
... | ||
// Transfer second token reward to the service multisig | ||
if (reward > 0) { | ||
_withdraw(multisig, reward); <--- mul ratio? | ||
} | ||
``` | ||
[] | ||
|
||
#### Notes: Available reward in second token. triple check and tests logic | ||
``` | ||
The logic looks confusing and somewhere it multiplies by ratio, sometime not. Please, triple check and test code! | ||
if (numServices > 0) { | ||
uint256 lastAvailableRewards = availableRewards; | ||
uint256 totalRewards; | ||
for (uint256 i = 0; i < numServices; ++i) { | ||
totalRewards += eligibleServiceRewards[i]; --> totalRewards += eligibleServiceRewards[i] --> without ratio | ||
} | ||
uint256 curServiceId; | ||
// If total allocated rewards are not enough, adjust the reward value | ||
if ((totalRewards * rewardRatio) / 1e18 > lastAvailableRewards) { --> totalRewards * rewardRatio vs lastAvailableRewards | ||
// Traverse all the eligible services and adjust their rewards proportional to leftovers | ||
// Note the algorithm is the exact copy of StakingBase logic | ||
uint256 updatedReward; | ||
uint256 updatedTotalRewards; | ||
for (uint256 i = 1; i < numServices; ++i) { | ||
// Calculate the updated reward | ||
updatedReward = (eligibleServiceRewards[i] * lastAvailableRewards) / totalRewards; | ||
// Add to the total updated reward | ||
updatedTotalRewards += updatedReward; | ||
curServiceId = eligibleServiceIds[i]; | ||
// Add reward to the overall service reward | ||
mapStakerInfos[curServiceId].reward += (updatedReward * rewardRatio) / 1e18; | ||
} | ||
// Process the first service in the set | ||
updatedReward = (eligibleServiceRewards[0] * lastAvailableRewards) / totalRewards; | ||
updatedTotalRewards += updatedReward; | ||
curServiceId = eligibleServiceIds[0]; | ||
// If the reward adjustment happened to have small leftovers, add it to the first service | ||
if (lastAvailableRewards > updatedTotalRewards) { | ||
updatedReward += lastAvailableRewards - updatedTotalRewards; | ||
} | ||
// Add reward to the overall service reward | ||
mapStakerInfos[curServiceId].reward += (updatedReward * rewardRatio) / 1e18; | ||
// Set available rewards to zero | ||
lastAvailableRewards = 0; | ||
} else { | ||
// Traverse all the eligible services and add to their rewards | ||
for (uint256 i = 0; i < numServices; ++i) { | ||
// Add reward to the service overall reward | ||
curServiceId = eligibleServiceIds[i]; | ||
mapStakerInfos[curServiceId].reward += (eligibleServiceRewards[i] * rewardRatio) / 1e18; | ||
} | ||
// Adjust available rewards | ||
lastAvailableRewards -= totalRewards; --> lastAvailableRewards vs totalRewards without ratio | ||
} | ||
// Update the storage value of available rewards | ||
availableRewards = lastAvailableRewards; --> lastAvailableRewards vs totalRewards without ratio | ||
} | ||
``` | ||
[] | ||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.28; | ||
|
||
import {ERC721TokenReceiver} from "../../lib/autonolas-registries/lib/solmate/src/tokens/ERC721.sol"; | ||
import {IService} from "./interfaces/IService.sol"; | ||
import {IStaking} from "./interfaces/IStaking.sol"; | ||
import {SafeTransferLib} from "../libraries/SafeTransferLib.sol"; | ||
|
@@ -28,7 +29,7 @@ error OwnerOnly(address sender, address owner); | |
error ReentrancyGuard(); | ||
|
||
struct StakerInfo { | ||
// Staking token amount | ||
// Second token amount | ||
uint256 stakingAmount; | ||
// Cumulative reward | ||
uint256 reward; | ||
|
@@ -40,29 +41,27 @@ struct StakerInfo { | |
/// @author Aleksandr Kuperman - <[email protected]> | ||
/// @author Andrey Lebedev - <[email protected]> | ||
/// @author Mariapia Moscatiello - <[email protected]> | ||
contract DualStakingToken { | ||
contract DualStakingToken is ERC721TokenReceiver { | ||
event OwnerUpdated(address indexed owner); | ||
event StakingTokenParamsUpdated(uint256 stakingTokenAmount, uint256 rewardRatio); | ||
event StakingTokenParamsUpdated(uint256 secondTokenAmount, uint256 rewardRatio); | ||
event Deposit(address indexed sender, uint256 amount, uint256 balance, uint256 availableRewards); | ||
event Withdraw(address indexed to, uint256 amount); | ||
|
||
// Service registry address | ||
address public immutable serviceRegistry; | ||
// Staking token address (except for OLAS) | ||
address public immutable stakingToken; | ||
// Service staking instance address | ||
// Second token address (except for OLAS) | ||
address public immutable secondToken; | ||
// OLAS service staking instance address | ||
address public immutable stakingInstance; | ||
// Required second token amount | ||
uint256 public immutable secondTokenAmount; | ||
// Second token ratio to OLAS rewards in 1e18 form | ||
uint256 public immutable rewardRatio; | ||
|
||
// Required staking token amount | ||
uint256 public stakingTokenAmount; | ||
// Staking token ratio to OLAS rewards in 1e18 form | ||
uint256 public rewardRatio; | ||
// Staking token contract balance | ||
// Second token contract balance | ||
uint256 public balance; | ||
// Staking token available rewards | ||
// Second token available rewards | ||
uint256 public availableRewards; | ||
// Owner address | ||
address public owner; | ||
|
||
// Reentrancy lock | ||
uint256 internal _locked = 1; | ||
|
@@ -74,17 +73,17 @@ contract DualStakingToken { | |
|
||
/// @dev DualStakingToken constructor. | ||
/// @param _serviceRegistry Service registry address. | ||
/// @param _stakingToken Staking token address. | ||
/// @param _secondToken Second token address. | ||
/// @param _stakingInstance Service staking instance address. | ||
/// @param _rewardRatio Staking token ratio to OLAS rewards in 1e18 form. | ||
/// @param _rewardRatio Second token ratio to OLAS rewards in 1e18 form. | ||
constructor( | ||
address _serviceRegistry, | ||
address _stakingToken, | ||
address _secondToken, | ||
address _stakingInstance, | ||
uint256 _rewardRatio | ||
) { | ||
// Check for zero addresses | ||
if (_serviceRegistry == address(0) || _stakingToken == address(0) || _stakingInstance == address(0)) { | ||
if (_serviceRegistry == address(0) || _secondToken == address(0) || _stakingInstance == address(0)) { | ||
revert ZeroAddress(); | ||
} | ||
|
||
|
@@ -94,18 +93,15 @@ contract DualStakingToken { | |
} | ||
|
||
serviceRegistry = _serviceRegistry; | ||
stakingToken = _stakingToken; | ||
secondToken = _secondToken; | ||
stakingInstance = _stakingInstance; | ||
|
||
rewardRatio = _rewardRatio; | ||
|
||
// Calculate staking token amount based on staking instance service information | ||
// Calculate second token amount based on staking instance service information | ||
uint256 numAgentInstances = IStaking(_stakingInstance).numAgentInstances(); | ||
uint256 minStakingDeposit = IStaking(_stakingInstance).minStakingDeposit(); | ||
// Total service deposit = minStakingDeposit + minStakingDeposit * numAgentInstances | ||
stakingTokenAmount = (minStakingDeposit * (1 + numAgentInstances) * _rewardRatio) / 1e18; | ||
|
||
owner = msg.sender; | ||
secondTokenAmount = (minStakingDeposit * (1 + numAgentInstances) * _rewardRatio) / 1e18; | ||
} | ||
|
||
/// @dev Withdraws the reward amount to a service owner. | ||
|
@@ -116,49 +112,12 @@ contract DualStakingToken { | |
// Update the contract balance | ||
balance -= amount; | ||
|
||
SafeTransferLib.safeTransfer(stakingToken, to, amount); | ||
SafeTransferLib.safeTransfer(secondToken, to, amount); | ||
|
||
emit Withdraw(to, amount); | ||
} | ||
|
||
/// @dev Changes contract owner address. | ||
/// @param newOwner Address of a new owner. | ||
function changeOwner(address newOwner) external { | ||
// Check for the ownership | ||
if (msg.sender != owner) { | ||
revert OwnerOnly(msg.sender, owner); | ||
} | ||
|
||
// Check for the zero address | ||
if (newOwner == address(0)) { | ||
revert ZeroAddress(); | ||
} | ||
|
||
owner = newOwner; | ||
emit OwnerUpdated(newOwner); | ||
} | ||
|
||
/// @dev Changes staking token params. | ||
/// @param newStakingTokenAmount New staking token amount. | ||
/// @param newRewardRatio New staking token ratio to OLAS rewards in 1e18 form. | ||
function changeStakingTokenParams(uint256 newStakingTokenAmount, uint256 newRewardRatio) external { | ||
// Check for the ownership | ||
if (msg.sender != owner) { | ||
revert OwnerOnly(msg.sender, owner); | ||
} | ||
|
||
// Check for zero values | ||
if (newStakingTokenAmount == 0 || newRewardRatio == 0) { | ||
revert ZeroValue(); | ||
} | ||
|
||
stakingTokenAmount = newStakingTokenAmount; | ||
rewardRatio = newRewardRatio; | ||
|
||
emit StakingTokenParamsUpdated(newStakingTokenAmount, newRewardRatio); | ||
} | ||
|
||
/// @dev Stakes OLAS service Id and required staking token amount. | ||
/// @dev Stakes OLAS service Id and required second token amount. | ||
/// @param serviceId OLAS driven service Id. | ||
function stake(uint256 serviceId) external { | ||
// Reentrancy guard | ||
|
@@ -167,13 +126,17 @@ contract DualStakingToken { | |
} | ||
_locked = 2; | ||
|
||
if (availableRewards == 0) { | ||
revert ZeroValue(); | ||
} | ||
|
||
StakerInfo storage stakerInfo = mapStakerInfos[serviceId]; | ||
// Check for existing staker | ||
if (stakerInfo.account != address(0)) { | ||
revert(); | ||
} | ||
|
||
uint256 amount = stakingTokenAmount; | ||
uint256 amount = secondTokenAmount; | ||
|
||
// Record staker info values | ||
stakerInfo.account = msg.sender; | ||
|
@@ -184,7 +147,7 @@ contract DualStakingToken { | |
|
||
mapMutisigs[multisig] = true; | ||
|
||
SafeTransferLib.safeTransferFrom(stakingToken, msg.sender, address(this), amount); | ||
SafeTransferLib.safeTransferFrom(secondToken, msg.sender, address(this), amount); | ||
|
||
IService(serviceRegistry).safeTransferFrom(msg.sender, address(this), serviceId); | ||
|
||
|
@@ -215,10 +178,10 @@ contract DualStakingToken { | |
IStaking(stakingInstance).checkpoint(); | ||
|
||
// Process rewards | ||
// If there are eligible services, calculate and update staking token rewards | ||
// If there are eligible services, calculate and update second token rewards | ||
uint256 numServices = eligibleServiceIds.length; | ||
if (numServices > 0) { | ||
uint256 lastAvailableRewards = availableRewards; | ||
uint256 lastAvailableRewards = availableRewards; | ||
if (numServices > 0 && lastAvailableRewards > 0) { | ||
uint256 totalRewards; | ||
for (uint256 i = 0; i < numServices; ++i) { | ||
totalRewards += eligibleServiceRewards[i]; | ||
|
@@ -308,7 +271,7 @@ contract DualStakingToken { | |
_locked = 1; | ||
} | ||
|
||
/// @dev Unstakes OLAS service Id and unbonds staking token amount. | ||
/// @dev Unstakes OLAS service Id and unbonds second token amount. | ||
/// @param serviceId OLAS driven service Id. | ||
function unstake(uint256 serviceId) external { | ||
// Reentrancy guard | ||
|
@@ -343,11 +306,10 @@ contract DualStakingToken { | |
delete mapStakerInfos[serviceId]; | ||
delete mapMutisigs[multisig]; | ||
|
||
// Transfer staking token amount back to the staker | ||
// Transfer second token amount back to the staker | ||
_withdraw(account, stakingAmount); | ||
|
||
// TODO Reward is sent to the service multisig or staker account? | ||
// Transfer staking token reward to the service multisig | ||
// Transfer second token reward to the service multisig | ||
if (reward > 0) { | ||
_withdraw(multisig, reward); | ||
} | ||
|
@@ -364,7 +326,7 @@ contract DualStakingToken { | |
_locked = 1; | ||
} | ||
|
||
/// @dev Claims OLAS and staking token rewards. | ||
/// @dev Claims OLAS and second token rewards. | ||
/// @param serviceId OLAS driven service Id. | ||
function claim(uint256 serviceId) external { | ||
// Reentrancy guard | ||
|
@@ -388,8 +350,7 @@ contract DualStakingToken { | |
// Get service multisig | ||
(, address multisig, , , , , ) = IService(serviceRegistry).mapServices(serviceId); | ||
|
||
// TODO Reward is sent to the service multisig or staker account? | ||
// Transfer staking token reward to the service multisig | ||
// Transfer second token reward to the service multisig | ||
if (reward > 0) { | ||
_withdraw(multisig, reward); | ||
} | ||
|
@@ -418,7 +379,7 @@ contract DualStakingToken { | |
availableRewards = newAvailableRewards; | ||
|
||
// Add to the overall balance | ||
SafeTransferLib.safeTransferFrom(stakingToken, msg.sender, address(this), amount); | ||
SafeTransferLib.safeTransferFrom(secondToken, msg.sender, address(this), amount); | ||
|
||
emit Deposit(msg.sender, amount, newBalance, newAvailableRewards); | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.