Skip to content

Commit

Permalink
improving simpleStaking but postconfirmation does not work
Browse files Browse the repository at this point in the history
  • Loading branch information
apenzk committed Feb 19, 2025
1 parent fd946ad commit 849e784
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 46 deletions.
45 changes: 33 additions & 12 deletions protocol-units/settlement/mcr/contracts/src/settlement/MCR.sol
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ contract MCR is Initializable, BaseSettlement, MCRStorage, IMCR {
stakingContract.acceptGenesisCeremony();
}

function computeTotalStake(
function getTotalStake(
uint256 epoch
) public view returns (uint256) {
// we can either use the attesterStake or the custodianStake
Expand All @@ -169,12 +169,12 @@ contract MCR is Initializable, BaseSettlement, MCRStorage, IMCR {
}


function computeTotalStakeForAcceptingEpoch()
function getTotalStakeForAcceptingEpoch()
public
view
returns (uint256)
{
return computeTotalStake(getAcceptingEpoch());
return getTotalStake(getAcceptingEpoch());
}

// gets the total stake for the accepting epoch
Expand All @@ -201,13 +201,14 @@ contract MCR is Initializable, BaseSettlement, MCRStorage, IMCR {
}

// Sets the last postconfirmed superBlock height.
function setLastPostconfirmedSuperBlockHeight(uint256 height) public {
require(
hasRole(COMMITMENT_ADMIN, msg.sender),
"SET_LAST_POSTCONFIRMED_SUPERBLOCK_HEIGHT_IS_COMMITMENT_ADMIN_ONLY"
);
lastPostconfirmedSuperBlockHeight = height;
}
// TODO: i have commented this out (so reconfirm) as we do not want to allow the commitment admin to set the last postconfirmed superblock height.
// function setLastPostconfirmedSuperBlockHeight(uint256 height) public {
// require(
// hasRole(COMMITMENT_ADMIN, msg.sender),
// "SET_LAST_POSTCONFIRMED_SUPERBLOCK_HEIGHT_IS_COMMITMENT_ADMIN_ONLY"
// );
// lastPostconfirmedSuperBlockHeight = height;
// }

// Forces the latest attestation by setting the superBlock height
// Note: this only safe when we are running with a single validator as it does not zero out follow-on commitments.
Expand Down Expand Up @@ -287,7 +288,8 @@ contract MCR is Initializable, BaseSettlement, MCRStorage, IMCR {
// if the current acceptor is live we should not accept postconfirmations from voluntary attesters
// TODO: we probably have to apply this check somewhere else as (volunteer) attesters can only postconfirm and rollover an epoch in which they are staked.
if (currentAcceptorIsLive()) {
if (attester != getCurrentAcceptor()) revert("NotAcceptorAndAcceptorIsLive");
// TODO: for now everyone can postconfirm, but change this later
// if (attester != getCurrentAcceptor()) revert("NotAcceptorAndAcceptorIsLive");
}

// keep ticking through postconfirmations and rollovers as long as the acceptor is permitted to do
Expand Down Expand Up @@ -357,7 +359,7 @@ contract MCR is Initializable, BaseSettlement, MCRStorage, IMCR {
// but since the operations we're doing are very cheap, the set actually adds overhead

// TODO the supermajority is 2f+1 from 3f+1 nodes. Not 2f from 3f.
uint256 supermajority = (2 * computeTotalStake(superBlockEpoch)) / 3;
uint256 supermajority = (2 * getTotalStake(superBlockEpoch)) / 3;
address[] memory attesters = getStakedAttestersForAcceptingEpoch();

// iterate over the attester set
Expand All @@ -366,6 +368,10 @@ contract MCR is Initializable, BaseSettlement, MCRStorage, IMCR {
for (uint256 i = 0; i < attesters.length; i++) {
address attester = attesters[i];
SuperBlockCommitment memory superBlockCommitment = commitments[superBlockHeight][attester];
// check if the commitment has committed to the correct superBlock height
// TODO: possibly this is not needed and we can remove the height from the commitment?
if (superBlockCommitment.height != superBlockHeight) continue;

// check the total stake on the commitment
uint256 totalStakeOnCommitment = commitmentStakes[superBlockCommitment.height][superBlockCommitment.commitment];
if (totalStakeOnCommitment > supermajority) {
Expand Down Expand Up @@ -480,4 +486,19 @@ contract MCR is Initializable, BaseSettlement, MCRStorage, IMCR {
// TODO: have an acceptor that can be set.
currentAcceptor = attesters[acceptorIndex];
}

/// @notice Gets the commitment submitted by an attester for a given height
function getCommitmentByAttester(uint256 height, address attester) public view returns (SuperBlockCommitment memory) {
return commitments[height][attester];
}

/// @notice Gets the height of the last postconfirmed superblock
function getLastPostconfirmedSuperBlockHeight() public view returns (uint256) {
return lastPostconfirmedSuperBlockHeight;
}

/// @notice Gets the epoch assigned to a superblock height
function getSuperBlockHeightAssignedEpoch(uint256 height) public view returns (uint256) {
return superBlockHeightAssignedEpoch[height];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ contract MovementStaking is
uint256 activeAttesterCount = 0;
for (uint256 i = 0; i < totalAttesters; i++) {
address attester = registeredAttestersByDomain[domain].at(i);
if (computeAllStakeForCurrentAcceptingEpoch(attester) > 0) {
if (getAttesterStakeForAcceptingEpoch(domain, attester) > 0) {
activeAttesterCount++;
}
}
Expand All @@ -84,7 +84,7 @@ contract MovementStaking is
uint256 activeIndex = 0;
for (uint256 i = 0; i < totalAttesters; i++) {
address attester = registeredAttestersByDomain[domain].at(i);
if (computeAllStakeForCurrentAcceptingEpoch(attester) > 0) {
if (getAttesterStakeForAcceptingEpoch(domain, attester) > 0) {
activeAttesters[activeIndex] = attester;
activeIndex++;
}
Expand Down Expand Up @@ -276,6 +276,18 @@ contract MovementStaking is
getCustodianStake(domain, getAcceptingEpoch(domain), custodian);
}

function getAttesterStake(address domain, uint256 epoch, address attester) public view returns (uint256) {
uint256 attesterStake = 0;
for (uint256 i = 0; i < registeredCustodiansByDomain[domain].length(); i++) {
attesterStake += getStake(domain, epoch, registeredCustodiansByDomain[domain].at(i), attester);
}
return attesterStake;
}

function getAttesterStakeForAcceptingEpoch(address domain, address attester) public view returns (uint256) {
return getAttesterStake(domain, getAcceptingEpoch(domain), attester);
}

// stakes for the next epoch
function stake(
address domain,
Expand Down Expand Up @@ -616,7 +628,7 @@ contract MovementStaking is

/// @notice Computes total stake across all custodians and attesters for the current accepting epoch
/// @param domain The domain to compute total stake for
function computeAllStakeForCurrentAcceptingEpoch(
function computeAllStakeForAcceptingEpoch(
address domain
) public view returns (uint256) {
return computeAllStake(domain, getAcceptingEpoch(domain));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,5 @@ interface IMovementStaking {
error GenesisAlreadyAccepted();

function getStakedAttestersForAcceptingEpoch(address domain) external view returns (address[] memory);
function computeAllStakeForCurrentAcceptingEpoch(address attester) external view returns (uint256);
function computeAllStakeForAcceptingEpoch(address attester) external view returns (uint256);
}
133 changes: 103 additions & 30 deletions protocol-units/settlement/mcr/contracts/test/settlement/MCR.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,15 @@ contract MCRTest is Test, IMCR {
assertNotEq(mcr.getCurrentAcceptor(), bob);

// make a block commitment
MCRStorage.SuperBlockCommitment memory bc1 = MCRStorage.SuperBlockCommitment({
MCRStorage.SuperBlockCommitment memory initCommitment = MCRStorage.SuperBlockCommitment({
height: 1,
commitment: keccak256(abi.encodePacked(uint256(1), uint256(2), uint256(3))),
blockId: keccak256(abi.encodePacked(uint256(1), uint256(2), uint256(3)))
});
vm.prank(alice);
mcr.submitSuperBlockCommitment(bc1);
mcr.submitSuperBlockCommitment(initCommitment);
vm.prank(bob);
mcr.submitSuperBlockCommitment(bc1);
mcr.submitSuperBlockCommitment(initCommitment);

// TODO these tests need to be split up into different test functions (happy / unhappy path)
// bob should not be the current acceptor
Expand All @@ -149,6 +149,7 @@ contract MCRTest is Test, IMCR {

}

/// @notice Test that the staking works as expected
function testSimpleStaking() public {
console.log("Starting testSimpleStaking");

Expand Down Expand Up @@ -185,55 +186,127 @@ contract MCRTest is Test, IMCR {

console.log("Alice staking...");
vm.prank(alice);
staking.stake(address(mcr), moveToken, 34);
console.log("Alice staked amount:", mcr.getStakeForAcceptingEpoch(address(moveToken), alice));
staking.stake(address(mcr), moveToken, 33);
uint256 aliceStake = mcr.getStakeForAcceptingEpoch(address(moveToken), alice);
console.log("Alice staked amount:", aliceStake);
assertEq(aliceStake, 33, "Alice's stake amount not correctly recorded");


console.log("\nBob approving and staking...");
vm.prank(bob);
moveToken.approve(address(staking), 100);
vm.prank(bob);
staking.stake(address(mcr), moveToken, 33);
console.log("Bob staked amount:", mcr.getStakeForAcceptingEpoch(address(moveToken), bob));
uint256 bobStake = mcr.getStakeForAcceptingEpoch(address(moveToken), bob);
console.log("Bob staked amount:", bobStake);
assertEq(bobStake, 33, "Bob's stake amount not correctly recorded");

console.log("\nCarol approving and staking...");
vm.prank(carol);
moveToken.approve(address(staking), 100);
vm.prank(carol);
staking.stake(address(mcr), moveToken, 33);
console.log("Carol staked amount:", mcr.getStakeForAcceptingEpoch(address(moveToken), carol));
staking.stake(address(mcr), moveToken, 34);
uint256 carolStake = mcr.getStakeForAcceptingEpoch(address(moveToken), carol);
console.log("Carol staked amount:", carolStake);
assertEq(carolStake, 34, "Carol's stake amount not correctly recorded");

// check that the total stake is 100
assertEq(mcr.getTotalStakeForAcceptingEpoch(), 100, "Total stake should be 100");
console.log("Total stake:", mcr.getTotalStakeForAcceptingEpoch());

// log the attester list
address[] memory attesters = staking.getStakedAttestersForAcceptingEpoch(address(mcr));
console.log("Attesters:", attesters.length);
assertEq(attesters.length, 3, "There should be 3 attesters");
for (uint256 i = 0; i < attesters.length; i++) {
console.log("Attester:", attesters[i]);
}

// end the genesis ceremony
console.log("\nAccepting genesis ceremony...");
mcr.acceptGenesisCeremony();
console.log("Genesis ceremony accepted");

// make a block commitment
console.log("\nMaking block commitment...");
MCRStorage.SuperBlockCommitment memory bc1 = MCRStorage.SuperBlockCommitment({
height: 1,

// check if there is a postconfirmed superblock
uint256 initHeight = mcr.getLastPostconfirmedSuperBlockHeight();
console.log("Last postconfirmed superblock height:", initHeight);
// retrieve the postconfirmed superblock at that height
MCRStorage.SuperBlockCommitment memory retrievedCommitmentEmpty = mcr.getPostconfirmedCommitment(initHeight);
console.log("Retrieved commitment height:", retrievedCommitmentEmpty.height);
console.log("Retrieved commitment:", uint256(retrievedCommitmentEmpty.commitment));

// make a superBlock commitment
uint256 targetHeight = 1;
console.log("\nMaking superBlock commitment...");
MCRStorage.SuperBlockCommitment memory initCommitment = MCRStorage.SuperBlockCommitment({
height: targetHeight,
commitment: keccak256(abi.encodePacked(uint256(1), uint256(2), uint256(3))),
blockId: keccak256(abi.encodePacked(uint256(1), uint256(2), uint256(3)))
});

console.log("Alice submitting commitment...");
vm.prank(alice);
mcr.submitSuperBlockCommitment(bc1);
mcr.submitSuperBlockCommitment(initCommitment);
// retrieve the commitment at height 1 (the one we submitted)
MCRStorage.SuperBlockCommitment memory retrievedCommitmentAlice = mcr.getCommitmentByAttester(targetHeight, alice);
console.log("Alice commitment :", uint256(retrievedCommitmentAlice.commitment));
assert(retrievedCommitmentAlice.commitment == initCommitment.commitment);

console.log("Bob submitting commitment...");
vm.prank(bob);
mcr.submitSuperBlockCommitment(bc1);
mcr.submitSuperBlockCommitment(initCommitment);
// retrieve the commitment
MCRStorage.SuperBlockCommitment memory retrievedCommitmentBob = mcr.getCommitmentByAttester(targetHeight, bob);
console.log("Bob commitment: ", uint256(retrievedCommitmentBob.commitment));
assert(retrievedCommitmentBob.commitment == initCommitment.commitment);

// who is the current acceptor?
// TODO: add acceptor role and check it is alice
// console.log("Current acceptor:", mcr.getCurrentAcceptor());

// check if acceptor is live, note that currentAcceptorIsLive() returns bool
console.log("Current acceptor is live:", mcr.currentAcceptorIsLive());
assert(mcr.currentAcceptorIsLive());
// check what is the assigned epoch for the height targetHeight
console.log("Assigned epoch for targetHeight:", mcr.getSuperBlockHeightAssignedEpoch(1));
console.log("Accepting epoch:", mcr.getAcceptingEpoch());
console.log("Present epoch:", mcr.getPresentEpoch());
// in this setup the accepting epoch is the same as the assigned epoch for the targetHeight
assertEq(mcr.getSuperBlockHeightAssignedEpoch(1), mcr.getAcceptingEpoch());


// alice postconfirms the superblock using attemptPostconfirm
console.log("Alice postconfirming...");
vm.prank(alice);
// can we make this so we catch any errors from
mcr.postconfirmSuperBlocks();


// check that the commitment is postconfirmed at targetHeight
MCRStorage.SuperBlockCommitment memory retrievedCommitmentAlicePostconfirmed = mcr.getPostconfirmedCommitment(targetHeight);
console.log("Alice postconfirmed - height:", retrievedCommitmentAlicePostconfirmed.height);
console.log("Alice postconfirmed - commitment:", uint256(retrievedCommitmentAlicePostconfirmed.commitment));
// check that the heights are correct
uint256 newHeight = mcr.getLastPostconfirmedSuperBlockHeight();
console.log("Last postconfirmed superblock height:", newHeight);
assertEq(newHeight, initHeight + 1);
assertEq(targetHeight, newHeight);

assert(retrievedCommitmentAlicePostconfirmed.commitment == initCommitment.commitment);
assert(retrievedCommitmentAlicePostconfirmed.height == targetHeight);

// now we move to block 2 and make some commitment just to trigger the epochRollover
console.log("\nRetrieving commitment...");
MCRStorage.SuperBlockCommitment memory retrievedCommitment = mcr.getPostconfirmedCommitment(1);
MCRStorage.SuperBlockCommitment memory retrievedCommitment = mcr.getPostconfirmedCommitment(targetHeight);
console.log("Retrieved commitment height:", retrievedCommitment.height);
console.log("Expected height:", bc1.height);
console.log("Expected height:", initCommitment.height);
console.log("Retrieved commitment:", toHexString(abi.encode(retrievedCommitment.commitment)));
console.log("Expected commitment:", toHexString(abi.encode(bc1.commitment)));
console.log("Expected commitment:", toHexString(abi.encode(initCommitment.commitment)));

assert(retrievedCommitment.commitment == bc1.commitment);
assert(retrievedCommitment.blockId == bc1.blockId);
assert(retrievedCommitment.height == 1);
assert(retrievedCommitment.commitment == initCommitment.commitment);
assert(retrievedCommitment.blockId == initCommitment.blockId);
assert(retrievedCommitment.height == targetHeight);
}

function testDishonestValidator() public {
Expand Down Expand Up @@ -280,20 +353,20 @@ contract MCRTest is Test, IMCR {
mcr.submitSuperBlockCommitment(dishonestCommitment);

// make a block commitment
MCRStorage.SuperBlockCommitment memory bc1 = MCRStorage.SuperBlockCommitment({
MCRStorage.SuperBlockCommitment memory initCommitment = MCRStorage.SuperBlockCommitment({
height: 1,
commitment: keccak256(abi.encodePacked(uint256(1), uint256(2), uint256(3))),
blockId: keccak256(abi.encodePacked(uint256(1), uint256(2), uint256(3)))
});
vm.prank(alice);
mcr.submitSuperBlockCommitment(bc1);
mcr.submitSuperBlockCommitment(initCommitment);
vm.prank(bob);
mcr.submitSuperBlockCommitment(bc1);
mcr.submitSuperBlockCommitment(initCommitment);

MCRStorage.SuperBlockCommitment memory retrievedCommitment = mcr.getPostconfirmedCommitment(1);
// now we move to block 2 and make some commitment just to trigger the epochRollover
assert(retrievedCommitment.commitment == bc1.commitment);
assert(retrievedCommitment.blockId == bc1.blockId);
assert(retrievedCommitment.commitment == initCommitment.commitment);
assert(retrievedCommitment.blockId == initCommitment.blockId);
assert(retrievedCommitment.height == 1);
}

Expand Down Expand Up @@ -343,15 +416,15 @@ contract MCRTest is Test, IMCR {
mcr.submitSuperBlockCommitment(dishonestCommitment);

// make a block commitment
MCRStorage.SuperBlockCommitment memory bc1 = MCRStorage.SuperBlockCommitment({
MCRStorage.SuperBlockCommitment memory initCommitment = MCRStorage.SuperBlockCommitment({
height: 1,
commitment: keccak256(abi.encodePacked(uint256(1), uint256(2), uint256(3))),
blockId: keccak256(abi.encodePacked(uint256(1), uint256(2), uint256(3)))
});
vm.prank(alice);
mcr.submitSuperBlockCommitment(bc1);
mcr.submitSuperBlockCommitment(initCommitment);
vm.prank(bob);
mcr.submitSuperBlockCommitment(bc1);
mcr.submitSuperBlockCommitment(initCommitment);

// now we move to block 2 and make some commitment just to trigger the epochRollover
vm.warp(310 seconds);
Expand All @@ -371,8 +444,8 @@ contract MCRTest is Test, IMCR {
assertEq(mcr.getStakeForAcceptingEpoch(address(moveToken), bob), 33);
assertEq(mcr.getStakeForAcceptingEpoch(address(moveToken), carol), 33);
MCRStorage.SuperBlockCommitment memory retrievedCommitment = mcr.getPostconfirmedCommitment(1);
assert(retrievedCommitment.commitment == bc1.commitment);
assert(retrievedCommitment.blockId == bc1.blockId);
assert(retrievedCommitment.commitment == initCommitment.commitment);
assert(retrievedCommitment.blockId == initCommitment.blockId);
assert(retrievedCommitment.height == 1);
}

Expand Down

0 comments on commit 849e784

Please sign in to comment.