contract GroupStaking {
IERC20 public token;
struct StakingGroup {
uint256 id;
uint256 totalAmount;
address[] members;
uint256[] weights;
bool exists;
}
// Mapping from group ID to group data
mapping(uint256 => StakingGroup) public stakingGroups;
// Current group ID counter
uint256 public nextGroupId = 1;
constructor(IERC20 _token) {
token = _token;
}
// Create a new staking group
function createStakingGroup(address[] calldata _members, uint256[] calldata _weights) external returns (uint256) {
require(_members.length > 0, "Empty members list");
require(_members.length == _weights.length, "Members and weights length mismatch");
// Validate weights sum to 100%
uint256 totalWeight = 0;
for (uint256 i = 0; i < _weights.length; i++) {
totalWeight += _weights[i];
}
require(totalWeight == 100, "Weights must sum to 100");
uint256 groupId = nextGroupId;
stakingGroups[groupId] = StakingGroup({
id: groupId,
totalAmount: 0,
members: _members,
weights: _weights,
exists: true
});
nextGroupId++;
return groupId;
}
// Stake tokens to a group
function stakeToGroup(uint256 _groupId, uint256 _amount) external {
require(stakingGroups[_groupId].exists, "Group does not exist");
require(token.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
stakingGroups[_groupId].totalAmount += _amount;
}
// Withdraw tokens from a group with rewards distributed according to weights
function withdrawFromGroup(uint256 _groupId, uint256 _amount) external {
StakingGroup storage group = stakingGroups[_groupId];
require(group.exists, "Group does not exist");
require(group.totalAmount >= _amount, "Insufficient group balance");
// Only a group member can initiate a withdrawal
bool isMember = false;
for (uint256 i = 0; i < group.members.length; i++) {
if (group.members[i] == msg.sender) {
isMember = true;
break;
}
}
require(isMember, "Not a group member");
// Update the group's total amount
group.totalAmount -= _amount;
// Distribute the withdrawn amount to all members according to their weights
// VULNERABLE: If any member is blacklisted, the entire distribution fails
for (uint256 i = 0; i < group.members.length; i++) {
uint256 memberShare = (_amount * group.weights[i]) / 100;
if (memberShare > 0) {
token.transfer(group.members[i], memberShare);
}
}
}
// Get group info
function getGroupInfo(uint256 _groupId) external view returns (
uint256 id,
uint256 totalAmount,
address[] memory members,
uint256[] memory weights
) {
StakingGroup storage group = stakingGroups[_groupId];
require(group.exists, "Group does not exist");
return (
group.id,
group.totalAmount,
group.members,
group.weights
);
}
}Решение: учитывайте возможность попадания в черный список.
К сожалению, здесь нет единственно «правильного» ответа. Каждая ситуация уникальна. Все зависит от структуры вашего протокола, терпимости к рискам и даже от ваших юридических соглашений с пользователями. По крайней мере, имейте в виду такую возможность и разработайте резервный механизм, обеспечивающий соблюдение нормативных требований.
Этот пункт чеклиста заставляет вас критически подумать о внешних зависимостях и их потенциальном влиянии на основные функции вашего протокола. Речь идет о том, чтобы предвидеть худшие сценарии и иметь хотя бы какой-то запасной план.
Минимальный пример и PoC, написанные в Foundry, доступны здесь.
#dos