Skip to content

Commit

Permalink
Adds Reserved Contributors to Multi-Contributor Contract
Browse files Browse the repository at this point in the history
At the deployment of the multi contributor contract the service node
operator has the ability to specify a number of reserved contributors
that must contribute to the contract for it to become a service node.
  • Loading branch information
darcys22 committed Sep 17, 2024
1 parent b23b938 commit 90329e0
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 26 deletions.
71 changes: 64 additions & 7 deletions contracts/ServiceNodeContribution.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ contract ServiceNodeContribution is Shared {
address[] public contributorAddresses;
uint256 public immutable maxContributors;

// Reserved Stakes
mapping(address => uint256) public reservedContributions;
address[] public reservedContributorAddresses;

// Smart Contract
bool public finalized = false;
bool public cancelled = false;
Expand Down Expand Up @@ -108,15 +112,38 @@ contract ServiceNodeContribution is Shared {
* is reverted.
* @param _blsSignature 128 byte BLS proof of possession signature that
* proves ownership of the `blsPubkey`.
* @param _reservedContributors array of address/amounts to reserve minimum stakes
* for the given addresses. Can be empty to leave contributor spots open to anyone.
*/
function contributeOperatorFunds(
uint256 amount,
IServiceNodeRewards.BLSSignatureParams memory _blsSignature
IServiceNodeRewards.BLSSignatureParams memory _blsSignature,
IServiceNodeRewards.Contributor[] memory _reservedContributors
) public onlyOperator {
require(contributorAddresses.length == 0, "Operator already contributed funds");
require(!cancelled, "Node has been cancelled.");
require(amount >= minimumContribution(), "Contribution is below minimum requirement");
blsSignature = _blsSignature;

uint256 reservedAmounts = amount;
uint256 reservedAmount = 0;
uint256 reservedMinimumContribution = 0;
for (uint256 i = 0; i < _reservedContributors.length; i++) {
require(_reservedContributors[i].addr != msg.sender, "Cannot reserve for operator");
require(reservedContributions[_reservedContributors[i].addr] == 0, "duplicate address in reserved contributors");
reservedMinimumContribution = calcMinimumContribution(
stakingRequirement - reservedAmounts,
i + 1,
maxContributors
);
reservedAmount = _reservedContributors[i].stakedAmount;
require(reservedAmount >= reservedMinimumContribution, "Contribution is below minimum requirement");
reservedAmounts += reservedAmount;
reservedContributorAddresses.push(_reservedContributors[i].addr);
reservedContributions[_reservedContributors[i].addr] = reservedAmount;
}
require(totalReservedContribution() <= stakingRequirement, "Reserved contributions too high");

contributeFunds(amount);
}

Expand Down Expand Up @@ -155,9 +182,24 @@ contract ServiceNodeContribution is Shared {
"been changed. Please inform the operator and pre-existing contributors to cancel "
"the contract, withdraw their funds and to re-initiate a new contract.");

uint256 reserved = reservedContributions[msg.sender];
if (reserved > 0) {
require(amount >= reserved, "Insufficient contribution for reserved contributor");
reservedContributions[msg.sender] = 0;
uint256 arrayLength = reservedContributorAddresses.length;
for (uint256 index = 0; index < arrayLength; index++) {
if (msg.sender == reservedContributorAddresses[index]) {
reservedContributorAddresses[index] = reservedContributorAddresses[arrayLength - 1];
reservedContributorAddresses.pop();
break;
}
}
} else {
require(amount >= minimumContribution(), "Contribution is below the minimum requirement.");
}

// NOTE: Check contract status and collateral
require(amount >= minimumContribution(), "Contribution is below the minimum requirement.");
require(totalContribution() + amount <= stakingRequirement, "Contribution exceeds the funding goal.");
require(totalContribution() + totalReservedContribution() + amount <= stakingRequirement, "Contribution exceeds the funding goal.");
require(!finalized, "Node has already been finalized.");
require(!cancelled, "Node has been cancelled.");

Expand Down Expand Up @@ -224,8 +266,11 @@ contract ServiceNodeContribution is Shared {
* amount must be greater than the minimum operator contribution which can
* be calculated by calling `calcMinimumContribution` with the staking
* requirement and 0 contributors.
* @param reservedContributors array of address/amounts to reserve minimum stakes
* for the given addresses. Can be empty to leave contributor spots open to anyone.
*/
function resetContract(uint256 amount) external onlyOperator {
function resetContract(uint256 amount, IServiceNodeRewards.Contributor[] memory reservedContributors) external onlyOperator {

require(finalized, "You cannot reset a contract that hasn't been finalised yet");

// NOTE: Zero out all addresses in `contributions`
Expand All @@ -240,7 +285,7 @@ contract ServiceNodeContribution is Shared {

// NOTE: Re-init the contract with the operator contribution.
finalized = false;
contributeOperatorFunds(amount, blsSignature);
contributeOperatorFunds(amount, blsSignature, reservedContributors);
}

/**
Expand Down Expand Up @@ -404,8 +449,8 @@ contract ServiceNodeContribution is Shared {
*/
function minimumContribution() public view returns (uint256 result) {
result = calcMinimumContribution(
stakingRequirement - totalContribution(),
contributorAddresses.length,
stakingRequirement - totalContribution() - totalReservedContribution(),
contributorAddresses.length + reservedContributorAddresses.length,
maxContributors
);
return result;
Expand Down Expand Up @@ -478,4 +523,16 @@ contract ServiceNodeContribution is Shared {
}
return result;
}

/**
* @notice Sum up all the reserved contributions recorded in the reserved list
*/
function totalReservedContribution() public view returns (uint256 result) {
uint256 arrayLength = reservedContributorAddresses.length;
for (uint256 i = 0; i < arrayLength; i++) {
address entry = reservedContributorAddresses[i];
result += reservedContributions[entry];
}
return result;
}
}
12 changes: 7 additions & 5 deletions contracts/test/ServiceNodeContributionEchidnaTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ contract ServiceNodeContributionEchidnaTest {
IServiceNodeRewards.BLSSignatureParams memory _blsSignature
) public {
mintTokensForTesting();
IServiceNodeRewards.Contributor[] memory reservedContributors;
if (
snOperator == msg.sender &&
snContribution.operatorContribution() == 0 &&
Expand All @@ -159,7 +160,7 @@ contract ServiceNodeContributionEchidnaTest {
assert(snContribution.totalContribution() == 0);
assert(snContribution.contributorAddressesLength() == 0);

try snContribution.contributeOperatorFunds(_amount, _blsSignature) {} catch {
try snContribution.contributeOperatorFunds(_amount, _blsSignature, reservedContributors) {} catch {
assert(false); // Contribute must succeed as all necessary preconditions are met
}

Expand All @@ -170,7 +171,7 @@ contract ServiceNodeContributionEchidnaTest {

assert(sentToken.balanceOf(msg.sender) == balanceBeforeContribute - _amount);
} else {
try snContribution.contributeOperatorFunds(_amount, _blsSignature) {
try snContribution.contributeOperatorFunds(_amount, _blsSignature, reservedContributors) {
assert(false); // Contribute as operator must not succeed
} catch {}
}
Expand Down Expand Up @@ -250,22 +251,23 @@ contract ServiceNodeContributionEchidnaTest {
}

function testResetContract(uint256 _amount) public {
IServiceNodeRewards.Contributor[] memory reservedContributors;
if (!snContribution.finalized() || snContribution.cancelled()) {
try snContribution.resetContract(_amount) {
try snContribution.resetContract(_amount, reservedContributors) {
assert(false); // Can't reset until after finalized
} catch {}
} else {
if (msg.sender == snOperator && _amount >= snContribution.minimumContribution()) {
uint256 balanceBeforeContribute = sentToken.balanceOf(msg.sender);
try snContribution.resetContract(_amount) {} catch {
try snContribution.resetContract(_amount, reservedContributors) {} catch {
assert(false); // Can reset if finalized
}
assert(!snContribution.finalized());
assert(snContribution.contributions(msg.sender) == _amount);
assert(snContribution.operatorContribution() == _amount);
assert(sentToken.balanceOf(msg.sender) == balanceBeforeContribute - _amount);
} else {
try snContribution.resetContract(_amount) {
try snContribution.resetContract(_amount, reservedContributors) {
assert(false); // Can not reset
} catch {}
}
Expand Down
Loading

0 comments on commit 90329e0

Please sign in to comment.