packages/contracts-core/test/suite/GasOracle.MinimumTips.t.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {IncorrectDestinationDomain, LocalGasDataNotSet, RemoteGasDataNotSet} from "../../contracts/libs/Errors.sol";
import {NumberLib} from "../../contracts/libs/stack/Number.sol";
import {Request, RequestLib} from "../../contracts/libs/stack/Request.sol";
import {Tips, TipsLib} from "../../contracts/libs/stack/Tips.sol";
import {TIPS_MULTIPLIER} from "../../contracts/libs/Constants.sol";
import {Random, GasOracle, GasOracleTest} from "./GasOracle.t.sol";
import {RawGasData, RawGasData256, RawRequest} from "../utils/libs/SynapseStructs.t.sol";
// solhint-disable func-name-mixedcase
// solhint-disable no-empty-blocks
// solhint-disable ordering
contract GasOracleMinimumTipsTest is GasOracleTest {
// We're using powers of two to avoid rounding errors (see NumberLib.compress()).
// Tips are stored with TIPS_MULTIPLIER=2**32, so we use values above 2**32.
uint256 public constant GAS_PRICE = 1 << 40;
uint256 public constant DATA_PRICE = 1 << 42;
uint256 public constant EXEC_BUFFER = 1 << 44;
uint256 public constant AMORT_ATT_COST = 1 << 46;
uint256 public constant HALF_BWAD = NumberLib.BWAD / 2;
uint256 public constant TWO_BWAD = NumberLib.BWAD * 2;
uint256 public constant FOUR_BWAD = NumberLib.BWAD * 4;
function setUp() public override {
super.setUp();
// Everything except etherPrice is not used and can be set to 0.
// Ether price for local chain is set to 0.5 ETH.
setLocalEtherPrice(HALF_BWAD);
// All values are going to be used.
// Ether price for remote chain is set to 2.0 ETH, markup is 50%.
testedGO().setGasData({
domain: DOMAIN_REMOTE,
gasPrice: GAS_PRICE,
dataPrice: DATA_PRICE,
execBuffer: EXEC_BUFFER,
amortAttCost: AMORT_ATT_COST,
etherPrice: TWO_BWAD,
markup: HALF_BWAD
});
}
// ══════════════════════════════════════════════ TESTS: REVERTS ═══════════════════════════════════════════════════
function test_getMinimumTips_revert_incorrectDestination() public {
vm.expectRevert(IncorrectDestinationDomain.selector);
testedGO().getMinimumTips(DOMAIN_LOCAL, 0, 0);
}
function test_getMinimumTips_revert_localGasDataNotSet() public {
setLocalEtherPrice(0);
vm.expectRevert(LocalGasDataNotSet.selector);
testedGO().getMinimumTips(DOMAIN_REMOTE, 0, 0);
}
function test_getMinimumTips_revert_remoteGasDataNotSet() public {
testedGO().setGasData({
domain: DOMAIN_REMOTE,
gasPrice: 0,
dataPrice: 0,
execBuffer: 0,
amortAttCost: 0,
etherPrice: 0,
markup: 0
});
vm.expectRevert(RemoteGasDataNotSet.selector);
testedGO().getMinimumTips(DOMAIN_REMOTE, 0, 0);
}
// ════════════════════════════════════════════ TESTS: MINIMUM TIPS ════════════════════════════════════════════════
function test_getMinimumTips_summitTip(Random memory random) public {
uint256 summitTip = 1 << 48;
testedGO().setSummitTip(summitTip);
uint256 paddedRequest = rawTestRequest(random).encodeRequest();
uint256 contentLength = random.nextUint16();
Tips tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
// Local chain Ether price is 0.5 ETH, so the summit tip is worth 2x.
assertEq(tips.summitTip() * TIPS_MULTIPLIER, summitTip * 2, "!summitTip: 0.5ETH");
// Local chain Ether price is 2.0 ETH, so the summit tip is worth 0.5x.
setLocalEtherPrice(TWO_BWAD);
tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
assertEq(tips.summitTip() * TIPS_MULTIPLIER, summitTip / 2, "!summitTip: 2.0ETH");
// Local chain Ether price is 4.0 ETH, so the summit tip is worth 0.25x.
setLocalEtherPrice(FOUR_BWAD);
tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
assertEq(tips.summitTip() * TIPS_MULTIPLIER, summitTip / 4, "!summitTip: 4.0ETH");
}
function test_getMinimumTips_attestationTip(Random memory random) public {
uint256 paddedRequest = rawTestRequest(random).encodeRequest();
uint256 contentLength = random.nextUint16();
Tips tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
// Remote chain Ether price is 2.0 ETH, local chain Ether price is 0.5 ETH.
// So the attestation tip is worth 4x.
assertEq(tips.attestationTip() * TIPS_MULTIPLIER, AMORT_ATT_COST * 4, "!attestationTip: 0.5ETH");
// Remote chain Ether price is 2.0 ETH, local chain Ether price is 2.0 ETH.
// So the attestation tip is worth 1x.
setLocalEtherPrice(TWO_BWAD);
tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
assertEq(tips.attestationTip() * TIPS_MULTIPLIER, AMORT_ATT_COST, "!attestationTip: 2.0ETH");
// Remote chain Ether price is 2.0 ETH, local chain Ether price is 4.0 ETH.
// So the attestation tip is worth 0.5x.
setLocalEtherPrice(FOUR_BWAD);
tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
assertEq(tips.attestationTip() * TIPS_MULTIPLIER, AMORT_ATT_COST / 2, "!attestationTip: 4.0ETH");
}
function test_getMinimumTips_executionTip(Random memory random) public {
RawRequest memory request = rawTestRequest(random);
uint256 paddedRequest = request.encodeRequest();
uint256 contentLength = random.nextUint16();
Tips tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
// Remote chain Ether price is 2.0 ETH, local chain Ether price is 0.5 ETH.
// So the execution tip is worth 4x.
// Execution tip is execBuffer + gasPrice * gasLimit + dataPrice * contentLength.
assertEq(
tips.executionTip() * TIPS_MULTIPLIER,
4 * (EXEC_BUFFER + GAS_PRICE * request.gasLimit + DATA_PRICE * contentLength),
"!executionTip: 0.5ETH"
);
// Remote chain Ether price is 2.0 ETH, local chain Ether price is 2.0 ETH.
// So the execution tip is worth 1x.
setLocalEtherPrice(TWO_BWAD);
tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
assertEq(
tips.executionTip() * TIPS_MULTIPLIER,
EXEC_BUFFER + GAS_PRICE * request.gasLimit + DATA_PRICE * contentLength,
"!executionTip: 2.0ETH"
);
// Remote chain Ether price is 2.0 ETH, local chain Ether price is 4.0 ETH.
// So the execution tip is worth 0.5x.
setLocalEtherPrice(FOUR_BWAD);
tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
assertEq(
tips.executionTip() * TIPS_MULTIPLIER,
(EXEC_BUFFER + GAS_PRICE * request.gasLimit + DATA_PRICE * contentLength) / 2,
"!executionTip: 4.0ETH"
);
}
function test_getMinimumTips_deliveryTip(Random memory random) public {
RawRequest memory request = rawTestRequest(random);
uint256 paddedRequest = request.encodeRequest();
uint256 contentLength = random.nextUint16();
Tips tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
// Remote chain Ether price is 2.0 ETH, local chain Ether price is 0.5 ETH.
// So the execution tip is worth 4x.
// Execution tip is execBuffer + gasPrice * gasLimit + dataPrice * contentLength.
// Delivery tip is execution tip * markup (50%), therefore the final value is worth 2x.
assertEq(
tips.deliveryTip() * TIPS_MULTIPLIER,
2 * (EXEC_BUFFER + GAS_PRICE * request.gasLimit + DATA_PRICE * contentLength),
"!deliveryTip: 0.5ETH"
);
// Remote chain Ether price is 2.0 ETH, local chain Ether price is 2.0 ETH.
// So the execution tip is worth 1x.
// Delivery tip is execution tip * markup (50%), therefore the final value is worth 0.5x.
setLocalEtherPrice(TWO_BWAD);
tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
assertEq(
tips.deliveryTip() * TIPS_MULTIPLIER,
(EXEC_BUFFER + GAS_PRICE * request.gasLimit + DATA_PRICE * contentLength) / 2,
"!deliveryTip: 2.0ETH"
);
// Remote chain Ether price is 2.0 ETH, local chain Ether price is 4.0 ETH.
// So the execution tip is worth 0.5x.
// Delivery tip is execution tip * markup (50%), therefore the final value is worth 0.25x.
setLocalEtherPrice(FOUR_BWAD);
tips = TipsLib.wrapPadded(testedGO().getMinimumTips(DOMAIN_REMOTE, paddedRequest, contentLength));
assertEq(
tips.deliveryTip() * TIPS_MULTIPLIER,
(EXEC_BUFFER + GAS_PRICE * request.gasLimit + DATA_PRICE * contentLength) / 4,
"!deliveryTip: 4.0ETH"
);
// TODO: adjust test when delivery tip will also include the value of gas airdrop.
}
function rawTestRequest(Random memory random) internal pure returns (RawRequest memory rr) {
rr = random.nextRequest();
// Set sensible max values for gasDrop and gasLimit.
rr.boundRequest({maxGasDrop: 10 ** 20, maxGasLimit: 10 ** 8});
}
function setLocalEtherPrice(uint256 etherPrice) public {
testedGO().setGasData({
domain: DOMAIN_LOCAL,
gasPrice: 0,
dataPrice: 0,
execBuffer: 0,
amortAttCost: 0,
etherPrice: etherPrice,
markup: 0
});
}
}