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

Fix: tips scaling in Summit #1463

Merged
merged 3 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions packages/contracts-core/contracts/Summit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.17;
// ══════════════════════════════ LIBRARY IMPORTS ══════════════════════════════
import {AttestationLib} from "./libs/memory/Attestation.sol";
import {ByteString} from "./libs/memory/ByteString.sol";
import {BONDING_OPTIMISTIC_PERIOD} from "./libs/Constants.sol";
import {BONDING_OPTIMISTIC_PERIOD, TIPS_GRANULARITY} from "./libs/Constants.sol";
import {MustBeSynapseDomain, NotaryInDispute, TipsClaimMoreThanEarned, TipsClaimZero} from "./libs/Errors.sol";
import {Receipt, ReceiptLib} from "./libs/memory/Receipt.sol";
import {Snapshot, SnapshotLib} from "./libs/memory/Snapshot.sol";
Expand Down Expand Up @@ -61,6 +61,9 @@ contract Summit is SnapshotHub, SummitEvents, InterfaceSummit {
uint64 deliveryTip;
}

/// @notice Struct for storing the actor tips for a given origin domain.
/// @param earned Total amount of tips earned by the actor, denominated in domain's wei
/// @param claimed Total amount of tips claimed by the actor, denominated in domain's wei
struct ActorTips {
uint128 earned;
uint128 claimed;
Expand Down Expand Up @@ -326,8 +329,12 @@ contract Summit is SnapshotHub, SummitEvents, InterfaceSummit {

/// @dev Award tip to any actor whether bonded or unbonded
function _awardActorTip(address actor, uint32 origin, uint64 tip) internal {
actorTips[actor][origin].earned += tip;
emit TipAwarded(actor, origin, tip);
// We need to do a shit here, as we operate with "scaled down" tips everywhere,
// but Summit is supposed to store the "full tip value".
// Tip fits into 64 bits, so it's safe to do a 32 bit shift without risk of overflow
uint128 tipAwarded = uint128(tip) << uint128(TIPS_GRANULARITY);
actorTips[actor][origin].earned += tipAwarded;
emit TipAwarded(actor, origin, tipAwarded);
}

/// @dev Award tip for posting Receipt to Summit contract.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ abstract contract SummitEvents {
* @notice Emitted when a tip is awarded to the actor, whether they are bonded or unbonded actor.
* @param actor Actor address
* @param origin Domain where tips were originally paid
* @param tip Tip value, scaled down by TIPS_MULTIPLIER
* @param tip Tip value, denominated in domain's wei
*/
event TipAwarded(address actor, uint32 origin, uint256 tip);
}
120 changes: 98 additions & 22 deletions packages/contracts-core/test/suite/SummitTips.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,9 @@ contract SummitTipsTest is AgentSecuredTest {
) public checkQueueLength(0) {
test_submitReceipt(re, tips, rtp, originZero, rcptNotaryIndex, attNotaryIndex, true);
skip(BONDING_OPTIMISTIC_PERIOD);
assertTrue(InterfaceSummit(summit).distributeTips());
rcptNotaryFinal = rcptNotary;
expectAwardedTipsEvents({re: re, tips: tips, isFirst: true, isFinal: true});
assertTrue(InterfaceSummit(summit).distributeTips());
checkAwardedTips(re, tips, true);
}

Expand All @@ -253,8 +254,9 @@ contract SummitTipsTest is AgentSecuredTest {
) public checkQueueLength(0) {
test_submitReceipt(re, tips, rtp, originZero, rcptNotaryIndex, attNotaryIndex, false);
skip(BONDING_OPTIMISTIC_PERIOD);
assertTrue(InterfaceSummit(summit).distributeTips());
rcptNotaryFinal = address(0);
expectAwardedTipsEvents({re: re, tips: tips, isFirst: true, isFinal: false});
assertTrue(InterfaceSummit(summit).distributeTips());
checkAwardedTips(re, tips, false);
}

Expand All @@ -276,6 +278,7 @@ contract SummitTipsTest is AgentSecuredTest {
(bytes memory rcptPayload, bytes memory rcptSignature) = signReceipt(rcptNotaryFinal, re);
inbox.submitReceipt(rcptPayload, rcptSignature, tips.encodeTips(), rtp.headerHash, rtp.bodyHash);
skip(BONDING_OPTIMISTIC_PERIOD);
expectAwardedTipsEvents({re: re, tips: tips, isFirst: false, isFinal: true});
assertTrue(InterfaceSummit(summit).distributeTips());
checkAwardedTips(re, tips, true);
}
Expand Down Expand Up @@ -374,33 +377,33 @@ contract SummitTipsTest is AgentSecuredTest {
if (rcptNotary == rcptNotaryFinal) {
if (rcptNotary == re.attNotary) {
// rcptNotary == rcptNotaryFinal == attNotary
checkActorTips(rcptNotary, re.origin, receiptTipFirst + receiptTipFinal + tips.attestationTip, 0);
checkEarnedActorTips(rcptNotary, re.origin, receiptTipFirst + receiptTipFinal + tips.attestationTip);
} else {
// rcptNotary == rcptNotaryFinal != attNotary
checkActorTips(rcptNotary, re.origin, receiptTipFirst + receiptTipFinal, 0);
checkActorTips(re.attNotary, re.origin, tips.attestationTip, 0);
checkEarnedActorTips(rcptNotary, re.origin, receiptTipFirst + receiptTipFinal);
checkEarnedActorTips(re.attNotary, re.origin, tips.attestationTip);
}
} else if (re.attNotary == rcptNotaryFinal) {
// rcptNotaryFinal == attNotary != rcptNotary
checkActorTips(rcptNotary, re.origin, receiptTipFirst, 0);
checkActorTips(re.attNotary, re.origin, receiptTipFinal + tips.attestationTip, 0);
checkEarnedActorTips(rcptNotary, re.origin, receiptTipFirst);
checkEarnedActorTips(re.attNotary, re.origin, receiptTipFinal + tips.attestationTip);
} else {
if (rcptNotary == re.attNotary) {
// rcptNotary == attNotary != rcptNotaryFinal
checkActorTips(rcptNotary, re.origin, receiptTipFirst + tips.attestationTip, 0);
checkEarnedActorTips(rcptNotary, re.origin, receiptTipFirst + tips.attestationTip);
} else {
// rcptNotary != attNotary != rcptNotaryFinal
checkActorTips(rcptNotary, re.origin, receiptTipFirst, 0);
checkActorTips(re.attNotary, re.origin, tips.attestationTip, 0);
checkEarnedActorTips(rcptNotary, re.origin, receiptTipFirst);
checkEarnedActorTips(re.attNotary, re.origin, tips.attestationTip);
}
if (isFinal) checkActorTips(rcptNotaryFinal, re.origin, receiptTipFinal, 0);
if (isFinal) checkEarnedActorTips(rcptNotaryFinal, re.origin, receiptTipFinal);
}
// Check non-bonded actors
if (re.firstExecutor == re.finalExecutor) {
checkActorTips(re.firstExecutor, re.origin, tips.executionTip + (isFinal ? tips.deliveryTip : 0), 0);
checkEarnedActorTips(re.firstExecutor, re.origin, tips.executionTip + (isFinal ? tips.deliveryTip : 0));
} else {
checkActorTips(re.firstExecutor, re.origin, tips.executionTip, 0);
if (isFinal) checkActorTips(re.finalExecutor, re.origin, tips.deliveryTip, 0);
checkEarnedActorTips(re.firstExecutor, re.origin, tips.executionTip);
if (isFinal) checkEarnedActorTips(re.finalExecutor, re.origin, tips.deliveryTip);
}
}

Expand All @@ -409,21 +412,94 @@ contract SummitTipsTest is AgentSecuredTest {
if (re.origin == origin0) {
// Tips for origin0 go to guard0 and notary0 (they were first to use it),
// regardless of what attestation was used
checkActorTips(guard0, re.origin, snapshotTip, 0);
checkActorTips(snapNotary0, re.origin, snapshotTip, 0);
checkEarnedActorTips(guard0, re.origin, snapshotTip);
checkEarnedActorTips(snapNotary0, re.origin, snapshotTip);
} else if (re.origin == origin1) {
// Tips for origin1 go to guard1 and notary1 (they were first to use it)
checkActorTips(guard1, re.origin, snapshotTip, 0);
checkActorTips(snapNotary1, re.origin, snapshotTip, 0);
checkEarnedActorTips(guard1, re.origin, snapshotTip);
checkEarnedActorTips(snapNotary1, re.origin, snapshotTip);
} else {
revert("Incorrect origin value");
}
}

function checkActorTips(address actor, uint32 origin_, uint128 earned, uint128 claimed) public {
(uint128 earned_, uint128 claimed_) = InterfaceSummit(summit).actorTips(actor, origin_);
assertEq(earned_, earned, "!earned");
assertEq(claimed_, claimed, "!claimed");
function expectAwardedTipsEvents(RawExecReceipt memory re, RawTips memory tips, bool isFirst, bool isFinal)
public
{
if (isFirst) {
expectAwardedTipsEventsFirstSubmit(re, tips);
}
expectAwardedTipsEventsReceiptTips(re, tips, isFirst, isFinal);
if (isFinal) {
expectAwardedTipsEventsFinalSubmit(re, tips);
}
}

function expectAwardedTipsEventsFirstSubmit(RawExecReceipt memory re, RawTips memory tips) public {
// In the first submit, tips are awarded to
// 1. The Guard and Notary who submitted snapshot (summit tips)
// 2. Notary who submitted attestation (attestation tips)
// 3. First Executor (execution tips)
uint64 snapshotTip = splitTip({tip: tips.summitTip, parts: 3, roundUp: false});
if (re.origin == origin0) {
// Tips for origin0 go to guard0 and notary0 (they were first to use it),
// regardless of what attestation was used
expectAwardedTipsEvent(guard0, re.origin, snapshotTip);
expectAwardedTipsEvent(snapNotary0, re.origin, snapshotTip);
} else if (re.origin == origin1) {
// Tips for origin1 go to guard1 and notary1 (they were first to use it)
expectAwardedTipsEvent(guard1, re.origin, snapshotTip);
expectAwardedTipsEvent(snapNotary1, re.origin, snapshotTip);
} else {
revert("Incorrect origin value");
}
expectAwardedTipsEvent(re.attNotary, re.origin, tips.attestationTip);
expectAwardedTipsEvent(re.firstExecutor, re.origin, tips.executionTip);
}

function expectAwardedTipsEventsReceiptTips(
RawExecReceipt memory re,
RawTips memory tips,
bool isFirst,
bool isFinal
) public {
// The receipt tips are awarded to Notary who submitted receipt
// The receipt tips are 1/3 of the snapshot tips (rounded up). These are then split:
// - First half (rounded down) of receipt tips are awarded to Receipt Notary who submitted first receipt
// - Second half (rounded up) of receipt tips are awarded to Receipt Notary who submitted final receipt
uint64 receiptTipFull = splitTip({tip: tips.summitTip, parts: 3, roundUp: true});
uint64 receiptTip;
address notary = rcptNotary;
if (isFirst && isFinal) {
receiptTip = receiptTipFull;
} else if (isFirst) {
receiptTip = splitTip({tip: receiptTipFull, parts: 2, roundUp: false});
} else if (isFinal) {
receiptTip = splitTip({tip: receiptTipFull, parts: 2, roundUp: true});
notary = rcptNotaryFinal;
} else {
revert("Incorrect isFirst and isFinal values");
}
expectAwardedTipsEvent(notary, re.origin, receiptTip);
}

function expectAwardedTipsEventsFinalSubmit(RawExecReceipt memory re, RawTips memory tips) public {
// In the final submit (successful execution) tips are awarded to
// 1. Final Executor (delivery tips)
expectAwardedTipsEvent(re.finalExecutor, re.origin, tips.deliveryTip);
}

function expectAwardedTipsEvent(address actor, uint32 origin_, uint128 earnedScaledDown) public {
uint256 earnedTips = 2 ** 32 * earnedScaledDown;
vm.expectEmit(summit);
emit TipAwarded(actor, origin_, earnedTips);
}

/// @dev We calculate the "scaled down" version of earned tips, i.e. divided by 2^32
/// `Summit` is supposed to store the full value, so we scale calculated value up by 2^32.
function checkEarnedActorTips(address actor, uint32 origin_, uint128 earnedScaledDown) public {
(uint256 earnedTips,) = InterfaceSummit(summit).actorTips(actor, origin_);
assertEq(earnedTips, 2 ** 32 * earnedScaledDown);
}

function logTips(RawTips memory tips) public {
Expand Down
Loading