Skip to content

Commit

Permalink
feat(ct): add a protocol fee to be collected during execution
Browse files Browse the repository at this point in the history
  • Loading branch information
sam-goldman committed Mar 16, 2023
1 parent 1b636d6 commit ea4bc1e
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 53 deletions.
7 changes: 7 additions & 0 deletions .changeset/bright-dodos-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@chugsplash/contracts': minor
'@chugsplash/plugins': patch
'@chugsplash/core': patch
---

Add a protocol fee to be collected during execution
144 changes: 94 additions & 50 deletions packages/contracts/contracts/ChugSplashManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
*/
event ExecutorPaymentClaimed(address indexed executor, uint256 amount);

event ProtocolPaymentClaimed(address indexed recipient, uint256 amount);

/**
* @notice Emitted when the owner withdraws ETH from this contract.
*
Expand Down Expand Up @@ -239,11 +241,13 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
*/
uint256 public immutable executorPaymentPercentage;

uint256 public immutable protocolPaymentPercentage;

/**
* @notice Mapping of executor addresses to the ETH amount stored in this contract that is
* owed to them.
*/
mapping(address => uint256) public debt;
mapping(address => uint256) public executorDebt;

/**
* @notice Maps an address to a boolean indicating if the address is allowed to propose bundles.
Expand All @@ -270,7 +274,9 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
/**
* @notice ETH amount that is owed to the executor.
*/
uint256 public totalDebt;
uint256 public totalExecutorDebt;

uint256 public totalProtocolDebt;

/**
* @notice Modifier that restricts access to the executor.
Expand Down Expand Up @@ -298,13 +304,15 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
ChugSplashRecorder _recorder,
uint256 _executionLockTime,
uint256 _ownerBondAmount,
uint256 _executorPaymentPercentage
uint256 _executorPaymentPercentage,
uint256 _protocolPaymentPercentage
) {
registry = _registry;
recorder = _recorder;
executionLockTime = _executionLockTime;
ownerBondAmount = _ownerBondAmount;
executorPaymentPercentage = _executorPaymentPercentage;
protocolPaymentPercentage = _protocolPaymentPercentage;
}

/**
Expand Down Expand Up @@ -338,6 +346,10 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
keccak256(abi.encode(_actionRoot, _targetRoot, _numActions, _numTargets, _configUri));
}

function totalDebt() public view returns (uint256) {
return totalExecutorDebt + totalProtocolDebt;
}

/**
* @notice Queries the selected executor for a given project/bundle.
*
Expand Down Expand Up @@ -442,7 +454,7 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
*/
function approveChugSplashBundle(bytes32 _bundleId) public onlyOwner {
require(
address(this).balance - totalDebt >= ownerBondAmount,
address(this).balance - totalDebt() >= ownerBondAmount,
"ChugSplashManager: insufficient balance in manager"
);

Expand Down Expand Up @@ -563,27 +575,32 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
emit ChugSplashBundleInitiated(activeBundleId, msg.sender);
recorder.announce("ChugSplashBundleInitiated");

// See the explanation in `executeChugSplashAction`.
uint256 gasUsed = 152778 + initialGasLeft - gasleft();

// Calculate the executor's payment.
uint256 executorPayment;
uint256 gasPrice;
if (block.chainid != 10 && block.chainid != 420) {
// Use the gas price for any network that isn't Optimism.
executorPayment = (tx.gasprice * gasUsed * (100 + executorPaymentPercentage)) / 100;
gasPrice = tx.gasprice;
} else if (block.chainid == 10) {
// Optimism mainnet does not include `tx.gasprice` in the transaction, so we hardcode
// its value here.
executorPayment = (1000000 * gasUsed * (100 + executorPaymentPercentage)) / 100;
gasPrice = 1000000;
} else {
// Optimism mainnet does not include `tx.gasprice` in the transaction, so we hardcode
// Optimism Goerli does not include `tx.gasprice` in the transaction, so we hardcode
// its value here.
executorPayment = (gasUsed * (100 + executorPaymentPercentage)) / 100;
gasPrice = 1;
}

// Add the executor's payment to the debt.
totalDebt += executorPayment;
debt[msg.sender] += executorPayment;
// See the explanation in `executeChugSplashAction`.
uint256 gasUsed = 152778 + initialGasLeft - gasleft();

uint256 executorPayment = (gasPrice * gasUsed * (100 + executorPaymentPercentage)) / 100;
uint256 protocolPayment = gasPrice * gasUsed * protocolPaymentPercentage;

// Add the executor's payment to the executor debt.
totalExecutorDebt += executorPayment;
executorDebt[msg.sender] += executorPayment;

// Add the protocol's payment to the protocol debt.
totalProtocolDebt += protocolPayment;
}

/**
Expand Down Expand Up @@ -689,6 +706,20 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
emit ChugSplashActionExecuted(activeBundleId, _action.proxy, msg.sender, _actionIndex);
recorder.announceWithData("ChugSplashActionExecuted", abi.encodePacked(_action.proxy));

uint256 gasPrice;
if (block.chainid != 10 && block.chainid != 420) {
// Use the gas price for any network that isn't Optimism.
gasPrice = tx.gasprice;
} else if (block.chainid == 10) {
// Optimism mainnet does not include `tx.gasprice` in the transaction, so we hardcode
// its value here.
gasPrice = 1000000;
} else {
// Optimism Goerli does not include `tx.gasprice` in the transaction, so we hardcode
// its value here.
gasPrice = 1;
}

// Estimate the amount of gas used in this call by subtracting the current gas left from the
// initial gas left. We add 152778 to this amount to account for the intrinsic gas cost
// (21k), the calldata usage, and the subsequent opcodes that occur when we add the
Expand All @@ -698,23 +729,15 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
// the side of safety by adding a larger value. TODO: Get a better estimate than 152778.
uint256 gasUsed = 152778 + initialGasLeft - gasleft();

// Calculate the executor's payment and add it to the debt owed to the executor.
uint256 executorPayment;
if (block.chainid != 10 && block.chainid != 420) {
// Use the gas price for any network that isn't Optimism.
executorPayment = (tx.gasprice * gasUsed * (100 + executorPaymentPercentage)) / 100;
} else if (block.chainid == 10) {
// Optimism mainnet does not include `tx.gasprice` in the transaction, so we hardcode
// its value here.
executorPayment = (1000000 * gasUsed * (100 + executorPaymentPercentage)) / 100;
} else {
// Optimism mainnet does not include `tx.gasprice` in the transaction, so we hardcode
// its value here.
executorPayment = (gasUsed * (100 + executorPaymentPercentage)) / 100;
}
uint256 executorPayment = (gasPrice * gasUsed * (100 + executorPaymentPercentage)) / 100;
uint256 protocolPayment = gasPrice * gasUsed * protocolPaymentPercentage;

// Add the executor's payment to the executor debt.
totalExecutorDebt += executorPayment;
executorDebt[msg.sender] += executorPayment;

totalDebt += executorPayment;
debt[msg.sender] += executorPayment;
// Add the protocol's payment to the protocol debt.
totalProtocolDebt += protocolPayment;
}

/**
Expand Down Expand Up @@ -803,27 +826,32 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
emit ChugSplashBundleCompleted(completedBundleId, msg.sender, bundle.actionsExecuted);
recorder.announce("ChugSplashBundleCompleted");

// See the explanation in `executeChugSplashAction`.
uint256 gasUsed = 152778 + initialGasLeft - gasleft();

// Calculate the executor's payment.
uint256 executorPayment;
uint256 gasPrice;
if (block.chainid != 10 && block.chainid != 420) {
// Use the gas price for any network that isn't Optimism.
executorPayment = (tx.gasprice * gasUsed * (100 + executorPaymentPercentage)) / 100;
gasPrice = tx.gasprice;
} else if (block.chainid == 10) {
// Optimism mainnet does not include `tx.gasprice` in the transaction, so we hardcode
// its value here.
executorPayment = (1000000 * gasUsed * (100 + executorPaymentPercentage)) / 100;
gasPrice = 1000000;
} else {
// Optimism mainnet does not include `tx.gasprice` in the transaction, so we hardcode
// Optimism Goerli does not include `tx.gasprice` in the transaction, so we hardcode
// its value here.
executorPayment = (gasUsed * (100 + executorPaymentPercentage)) / 100;
gasPrice = 1;
}

// Add the executor's payment to the debt.
totalDebt += executorPayment;
debt[msg.sender] += executorPayment;
// See the explanation in `executeChugSplashAction`.
uint256 gasUsed = 152778 + initialGasLeft - gasleft();

uint256 executorPayment = (gasPrice * gasUsed * (100 + executorPaymentPercentage)) / 100;
uint256 protocolPayment = gasPrice * gasUsed * protocolPaymentPercentage;

// Add the executor's payment to the executor debt.
totalExecutorDebt += executorPayment;
executorDebt[msg.sender] += executorPayment;

// Add the protocol's payment to the protocol debt.
totalProtocolDebt += protocolPayment;
}

/**
Expand All @@ -844,7 +872,7 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
if (bundle.timeClaimed + executionLockTime >= block.timestamp) {
// Give the owner's bond to the executor if the bundle is cancelled within the
// `executionLockTime` window.
totalDebt += ownerBondAmount;
totalExecutorDebt += ownerBondAmount;
}

bytes32 cancelledBundleId = activeBundleId;
Expand Down Expand Up @@ -885,18 +913,34 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
* ETH that is owed to them by this contract.
*/
function claimExecutorPayment() external onlyExecutor {
uint256 amount = debt[msg.sender];
uint256 amount = executorDebt[msg.sender];

debt[msg.sender] -= amount;
totalDebt -= amount;
executorDebt[msg.sender] -= amount;
totalExecutorDebt -= amount;

(bool success, ) = payable(msg.sender).call{ value: amount }(new bytes(0));
require(success, "ChugSplashManager: call to withdraw owner funds failed");
require(success, "ChugSplashManager: call to withdraw executor funds failed");

emit ExecutorPaymentClaimed(msg.sender, amount);
recorder.announce("ExecutorPaymentClaimed");
}

function claimProtocolPayment() external {
require(
registry.protocolPaymentRecipients(msg.sender) == true,
"ChugSplashManager: caller is not a protocol payment recipient"
);

uint256 amount = totalProtocolDebt;
totalProtocolDebt = 0;

(bool success, ) = payable(msg.sender).call{ value: amount }(new bytes(0));
require(success, "ChugSplashManager: call to withdraw protocol funds failed");

emit ProtocolPaymentClaimed(msg.sender, amount);
recorder.announce("ProtocolPaymentClaimed");
}

/**
* @notice Transfers ownership of a proxy from this contract to the project owner.
*
Expand Down Expand Up @@ -934,7 +978,7 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
"ChugSplashManager: cannot withdraw funds while bundle is active"
);

uint256 amount = address(this).balance - totalDebt;
uint256 amount = address(this).balance - totalDebt();
(bool success, ) = payable(msg.sender).call{ value: amount }(new bytes(0));
require(success, "ChugSplashManager: call to withdraw owner funds failed");

Expand Down
24 changes: 24 additions & 0 deletions packages/contracts/contracts/ChugSplashRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ contract ChugSplashRegistry is Initializable, OwnableUpgradeable {
*/
event ExecutorRemoved(address indexed executor);

event ProtocolPaymentRecipientAdded(address indexed executor);

event ProtocolPaymentRecipientRemoved(address indexed executor);

/**
* @notice Mapping of project names to ChugSplashManager contracts.
*/
Expand All @@ -70,6 +74,8 @@ contract ChugSplashRegistry is Initializable, OwnableUpgradeable {
*/
mapping(address => bool) public executors;

mapping(address => bool) public protocolPaymentRecipients;

ChugSplashRecorder public recorder;

/**
Expand Down Expand Up @@ -189,6 +195,24 @@ contract ChugSplashRegistry is Initializable, OwnableUpgradeable {
emit ExecutorRemoved(_executor);
}

function addProtocolPaymentRecipient(address _recipient) external onlyOwner {
require(
protocolPaymentRecipients[_recipient] == false,
"ChugSplashRegistry: recipient already added"
);
protocolPaymentRecipients[_recipient] = true;
emit ProtocolPaymentRecipientAdded(_recipient);
}

function removeProtocolPaymentRecipient(address _recipient) external onlyOwner {
require(
protocolPaymentRecipients[_recipient] == true,
"ChugSplashRegistry: recipient already removed"
);
protocolPaymentRecipients[_recipient] = false;
emit ProtocolPaymentRecipientRemoved(_recipient);
}

/**
* @notice Internal function that gets the ChugSplashManager implementation address. Will only
* return a valid value when this contract is delegatecalled by the
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS =
export const OWNER_BOND_AMOUNT = ethers.utils.parseEther('0.001')
export const EXECUTION_LOCK_TIME = 15 * 60
export const EXECUTOR_PAYMENT_PERCENTAGE = 20
export const PROTOCOL_PAYMENT_PERCENTAGE = 20

export const CHUGSPLASH_BOOTLOADER_ADDRESS = ethers.utils.getCreate2Address(
DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
DEFAULT_UPDATER_ADDRESS,
registryProxyConstructorArgValues,
proxyInitializerConstructorArgValues,
PROTOCOL_PAYMENT_PERCENTAGE,
ChugSplashManagerABI,
DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS,
CHUGSPLASH_SALT,
Expand Down Expand Up @@ -59,6 +60,7 @@ export const chugsplashManagerConstructorArgs = {
_executionLockTime: EXECUTION_LOCK_TIME,
_ownerBondAmount: OWNER_BOND_AMOUNT.toString(),
_executorPaymentPercentage: EXECUTOR_PAYMENT_PERCENTAGE,
_protocolPaymentPercentage: PROTOCOL_PAYMENT_PERCENTAGE,
}

export const CHUGSPLASH_CONSTRUCTOR_ARGS = {}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ export const claimExecutorPayment = async (
executor: Wallet,
ChugSplashManager: Contract
) => {
const executorDebt = await ChugSplashManager.debt(executor.address)
const executorDebt = await ChugSplashManager.executorDebt(executor.address)
if (executorDebt.gt(0)) {
await (
await ChugSplashManager.claimExecutorPayment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const config: UserChugSplashConfig = {
'UUPSUpgradeable:__gap': [],
_roles: [],
},
externalProxy: '0x62DB6c1678Ca81ea0d946EA3dd75b4F71421A2aE',
externalProxy: '0x9A7848b9E60C7619f162880c7CA5Cbca80998034',
externalProxyType: 'oz-access-control-uups',
// We must specify these explicitly because newer versions of OpenZeppelin's Hardhat plugin
// don't create the Network file in the `.openzeppelin/` folder anymore:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const config: UserChugSplashConfig = {
'UUPSUpgradeable:__gap': [],
_owner: '{ preserve }',
},
externalProxy: '0xA7c8B0D74b68EF10511F27e97c379FB1651e1eD2',
externalProxy: '0xE9061F92bA9A3D9ef3f4eb8456ac9E552B3Ff5C8',
externalProxyType: 'oz-ownable-uups',
// We must specify these explicitly because newer versions of OpenZeppelin's Hardhat plugin
// don't create the Network file in the `.openzeppelin/` folder anymore:
Expand Down

0 comments on commit ea4bc1e

Please sign in to comment.