diff --git a/solidity/contracts/extensions/AccountingExtension.sol b/solidity/contracts/extensions/AccountingExtension.sol index abfec376..214251b0 100644 --- a/solidity/contracts/extensions/AccountingExtension.sol +++ b/solidity/contracts/extensions/AccountingExtension.sol @@ -1,163 +1,163 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; - -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -// import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; -// import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; - -// import {IAccountingExtension} from '../../interfaces/extensions/IAccountingExtension.sol'; - -// contract AccountingExtension is IAccountingExtension { -// using SafeERC20 for IERC20; -// using EnumerableSet for EnumerableSet.AddressSet; - -// /// @inheritdoc IAccountingExtension -// IOracle public immutable ORACLE; - -// /// @inheritdoc IAccountingExtension -// mapping(address _bonder => mapping(IERC20 _token => uint256 _balance)) public balanceOf; - -// /// @inheritdoc IAccountingExtension -// mapping(address _bonder => mapping(IERC20 _token => mapping(bytes32 _requestId => uint256 _amount))) public -// bondedAmountOf; - -// /** -// * @notice Storing which modules have the users approved to bond their tokens. -// */ -// mapping(address _bonder => EnumerableSet.AddressSet _modules) internal _approvals; - -// constructor(IOracle _oracle) { -// ORACLE = _oracle; -// } - -// /** -// * @notice Checks that the caller is an allowed module used in the request. -// */ -// modifier onlyAllowedModule(bytes32 _requestId) { -// if (!ORACLE.allowedModule(_requestId, msg.sender)) revert AccountingExtension_UnauthorizedModule(); -// _; -// } - -// modifier onlyParticipant(bytes32 _requestId, address _user) { -// if (!ORACLE.isParticipant(_requestId, _user)) revert AccountingExtension_UnauthorizedUser(); -// _; -// } - -// /// @inheritdoc IAccountingExtension -// function deposit(IERC20 _token, uint256 _amount) external { -// _token.safeTransferFrom(msg.sender, address(this), _amount); -// balanceOf[msg.sender][_token] += _amount; -// emit Deposited(msg.sender, _token, _amount); -// } - -// /// @inheritdoc IAccountingExtension -// function withdraw(IERC20 _token, uint256 _amount) external { -// uint256 _balance = balanceOf[msg.sender][_token]; - -// if (_balance < _amount) revert AccountingExtension_InsufficientFunds(); - -// unchecked { -// balanceOf[msg.sender][_token] -= _amount; -// } - -// _token.safeTransfer(msg.sender, _amount); - -// emit Withdrew(msg.sender, _token, _amount); -// } - -// /// @inheritdoc IAccountingExtension -// function pay( -// bytes32 _requestId, -// address _payer, -// address _receiver, -// IERC20 _token, -// uint256 _amount -// ) external onlyAllowedModule(_requestId) onlyParticipant(_requestId, _payer) onlyParticipant(_requestId, _receiver) { -// if (bondedAmountOf[_payer][_token][_requestId] < _amount) { -// revert AccountingExtension_InsufficientFunds(); -// } - -// balanceOf[_receiver][_token] += _amount; - -// unchecked { -// bondedAmountOf[_payer][_token][_requestId] -= _amount; -// } - -// emit Paid({_requestId: _requestId, _beneficiary: _receiver, _payer: _payer, _token: _token, _amount: _amount}); -// } - -// /// @inheritdoc IAccountingExtension -// function bond( -// address _bonder, -// bytes32 _requestId, -// IERC20 _token, -// uint256 _amount -// ) external onlyAllowedModule(_requestId) onlyParticipant(_requestId, _bonder) { -// if (!_approvals[_bonder].contains(msg.sender)) revert AccountingExtension_InsufficientAllowance(); -// if (balanceOf[_bonder][_token] < _amount) revert AccountingExtension_InsufficientFunds(); - -// bondedAmountOf[_bonder][_token][_requestId] += _amount; - -// unchecked { -// balanceOf[_bonder][_token] -= _amount; -// } - -// emit Bonded(_requestId, _bonder, _token, _amount); -// } - -// /// @inheritdoc IAccountingExtension -// function bond( -// address _bonder, -// bytes32 _requestId, -// IERC20 _token, -// uint256 _amount, -// address _sender -// ) external onlyAllowedModule(_requestId) onlyParticipant(_requestId, _bonder) { -// if (!(_approvals[_bonder].contains(msg.sender) || _approvals[_bonder].contains(_sender))) { -// revert AccountingExtension_InsufficientAllowance(); -// } -// if (balanceOf[_bonder][_token] < _amount) revert AccountingExtension_InsufficientFunds(); - -// bondedAmountOf[_bonder][_token][_requestId] += _amount; - -// unchecked { -// balanceOf[_bonder][_token] -= _amount; -// } - -// emit Bonded(_requestId, _bonder, _token, _amount); -// } - -// /// @inheritdoc IAccountingExtension -// function release( -// address _bonder, -// bytes32 _requestId, -// IERC20 _token, -// uint256 _amount -// ) external onlyAllowedModule(_requestId) onlyParticipant(_requestId, _bonder) { -// if (bondedAmountOf[_bonder][_token][_requestId] < _amount) revert AccountingExtension_InsufficientFunds(); - -// balanceOf[_bonder][_token] += _amount; - -// unchecked { -// bondedAmountOf[_bonder][_token][_requestId] -= _amount; -// } - -// emit Released(_requestId, _bonder, _token, _amount); -// } - -// /// @inheritdoc IAccountingExtension -// function approveModule(address _module) external { -// _approvals[msg.sender].add(_module); -// } - -// /// @inheritdoc IAccountingExtension -// function revokeModule(address _module) external { -// _approvals[msg.sender].remove(_module); -// } - -// /// @inheritdoc IAccountingExtension -// function approvedModules(address _user) external view returns (address[] memory _approvedModules) { -// _approvedModules = _approvals[_user].values(); -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; + +import {IAccountingExtension} from '../../interfaces/extensions/IAccountingExtension.sol'; + +contract AccountingExtension is IAccountingExtension { + using SafeERC20 for IERC20; + using EnumerableSet for EnumerableSet.AddressSet; + + /// @inheritdoc IAccountingExtension + IOracle public immutable ORACLE; + + /// @inheritdoc IAccountingExtension + mapping(address _bonder => mapping(IERC20 _token => uint256 _balance)) public balanceOf; + + /// @inheritdoc IAccountingExtension + mapping(address _bonder => mapping(IERC20 _token => mapping(bytes32 _requestId => uint256 _amount))) public + bondedAmountOf; + + /** + * @notice Storing which modules have the users approved to bond their tokens. + */ + mapping(address _bonder => EnumerableSet.AddressSet _modules) internal _approvals; + + constructor(IOracle _oracle) { + ORACLE = _oracle; + } + + /** + * @notice Checks that the caller is an allowed module used in the request. + */ + modifier onlyAllowedModule(bytes32 _requestId) { + if (!ORACLE.allowedModule(_requestId, msg.sender)) revert AccountingExtension_UnauthorizedModule(); + _; + } + + modifier onlyParticipant(bytes32 _requestId, address _user) { + if (!ORACLE.isParticipant(_requestId, _user)) revert AccountingExtension_UnauthorizedUser(); + _; + } + + /// @inheritdoc IAccountingExtension + function deposit(IERC20 _token, uint256 _amount) external { + _token.safeTransferFrom(msg.sender, address(this), _amount); + balanceOf[msg.sender][_token] += _amount; + emit Deposited(msg.sender, _token, _amount); + } + + /// @inheritdoc IAccountingExtension + function withdraw(IERC20 _token, uint256 _amount) external { + uint256 _balance = balanceOf[msg.sender][_token]; + + if (_balance < _amount) revert AccountingExtension_InsufficientFunds(); + + unchecked { + balanceOf[msg.sender][_token] -= _amount; + } + + _token.safeTransfer(msg.sender, _amount); + + emit Withdrew(msg.sender, _token, _amount); + } + + /// @inheritdoc IAccountingExtension + function pay( + bytes32 _requestId, + address _payer, + address _receiver, + IERC20 _token, + uint256 _amount + ) external onlyAllowedModule(_requestId) onlyParticipant(_requestId, _payer) onlyParticipant(_requestId, _receiver) { + if (bondedAmountOf[_payer][_token][_requestId] < _amount) { + revert AccountingExtension_InsufficientFunds(); + } + + balanceOf[_receiver][_token] += _amount; + + unchecked { + bondedAmountOf[_payer][_token][_requestId] -= _amount; + } + + emit Paid({_requestId: _requestId, _beneficiary: _receiver, _payer: _payer, _token: _token, _amount: _amount}); + } + + /// @inheritdoc IAccountingExtension + function bond( + address _bonder, + bytes32 _requestId, + IERC20 _token, + uint256 _amount + ) external onlyAllowedModule(_requestId) onlyParticipant(_requestId, _bonder) { + if (!_approvals[_bonder].contains(msg.sender)) revert AccountingExtension_InsufficientAllowance(); + if (balanceOf[_bonder][_token] < _amount) revert AccountingExtension_InsufficientFunds(); + + bondedAmountOf[_bonder][_token][_requestId] += _amount; + + unchecked { + balanceOf[_bonder][_token] -= _amount; + } + + emit Bonded(_requestId, _bonder, _token, _amount); + } + + /// @inheritdoc IAccountingExtension + function bond( + address _bonder, + bytes32 _requestId, + IERC20 _token, + uint256 _amount, + address _sender + ) external onlyAllowedModule(_requestId) onlyParticipant(_requestId, _bonder) { + if (!(_approvals[_bonder].contains(msg.sender) || _approvals[_bonder].contains(_sender))) { + revert AccountingExtension_InsufficientAllowance(); + } + if (balanceOf[_bonder][_token] < _amount) revert AccountingExtension_InsufficientFunds(); + + bondedAmountOf[_bonder][_token][_requestId] += _amount; + + unchecked { + balanceOf[_bonder][_token] -= _amount; + } + + emit Bonded(_requestId, _bonder, _token, _amount); + } + + /// @inheritdoc IAccountingExtension + function release( + address _bonder, + bytes32 _requestId, + IERC20 _token, + uint256 _amount + ) external onlyAllowedModule(_requestId) onlyParticipant(_requestId, _bonder) { + if (bondedAmountOf[_bonder][_token][_requestId] < _amount) revert AccountingExtension_InsufficientFunds(); + + balanceOf[_bonder][_token] += _amount; + + unchecked { + bondedAmountOf[_bonder][_token][_requestId] -= _amount; + } + + emit Released(_requestId, _bonder, _token, _amount); + } + + /// @inheritdoc IAccountingExtension + function approveModule(address _module) external { + _approvals[msg.sender].add(_module); + } + + /// @inheritdoc IAccountingExtension + function revokeModule(address _module) external { + _approvals[msg.sender].remove(_module); + } + + /// @inheritdoc IAccountingExtension + function approvedModules(address _user) external view returns (address[] memory _approvedModules) { + _approvedModules = _approvals[_user].values(); + } +} diff --git a/solidity/contracts/extensions/BondEscalationAccounting.sol b/solidity/contracts/extensions/BondEscalationAccounting.sol index 3c75595d..3efe1e2a 100644 --- a/solidity/contracts/extensions/BondEscalationAccounting.sol +++ b/solidity/contracts/extensions/BondEscalationAccounting.sol @@ -1,134 +1,134 @@ -// // SPDX-License-Identifier: AGPL-3.0-only -// pragma solidity ^0.8.19; - -// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; - -// import {AccountingExtension} from './AccountingExtension.sol'; - -// import {IBondEscalationAccounting} from '../../interfaces/extensions/IBondEscalationAccounting.sol'; -// import {IBondEscalationModule} from '../../interfaces/modules/dispute/IBondEscalationModule.sol'; - -// contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccounting { -// /// @inheritdoc IBondEscalationAccounting -// mapping(bytes32 _disputeId => mapping(IERC20 _token => uint256 _amount)) public pledges; - -// /// @inheritdoc IBondEscalationAccounting -// mapping(bytes32 _disputeId => EscalationResult _result) public escalationResults; - -// /// @inheritdoc IBondEscalationAccounting -// mapping(bytes32 _requestId => mapping(address _pledger => bool)) public pledgerClaimed; - -// constructor(IOracle _oracle) AccountingExtension(_oracle) {} - -// /// @inheritdoc IBondEscalationAccounting -// function pledge( -// address _pledger, -// bytes32 _requestId, -// bytes32 _disputeId, -// IERC20 _token, -// uint256 _amount -// ) external onlyAllowedModule(_requestId) { -// if (balanceOf[_pledger][_token] < _amount) revert BondEscalationAccounting_InsufficientFunds(); - -// pledges[_disputeId][_token] += _amount; - -// unchecked { -// balanceOf[_pledger][_token] -= _amount; -// } - -// emit Pledged({_pledger: _pledger, _requestId: _requestId, _disputeId: _disputeId, _token: _token, _amount: _amount}); -// } - -// /// @inheritdoc IBondEscalationAccounting -// function onSettleBondEscalation( -// bytes32 _requestId, -// bytes32 _disputeId, -// bool _forVotesWon, -// IERC20 _token, -// uint256 _amountPerPledger, -// uint256 _winningPledgersLength -// ) external onlyAllowedModule(_requestId) { -// // TODO: check that flooring at _amountPerPledger calculation doesn't mess with this check -// if (pledges[_disputeId][_token] < _amountPerPledger * _winningPledgersLength) { -// revert BondEscalationAccounting_InsufficientFunds(); -// } - -// if (escalationResults[_disputeId].requestId != bytes32(0)) { -// revert BondEscalationAccounting_AlreadySettled(); -// } - -// escalationResults[_disputeId] = EscalationResult({ -// requestId: _requestId, -// forVotesWon: _forVotesWon, -// token: _token, -// amountPerPledger: _amountPerPledger, -// bondEscalationModule: IBondEscalationModule(msg.sender) -// }); - -// emit BondEscalationSettled({ -// _requestId: _requestId, -// _disputeId: _disputeId, -// _forVotesWon: _forVotesWon, -// _token: _token, -// _amountPerPledger: _amountPerPledger, -// _winningPledgersLength: _winningPledgersLength -// }); -// } - -// /// @inheritdoc IBondEscalationAccounting -// function claimEscalationReward(bytes32 _disputeId, address _pledger) external { -// EscalationResult memory _result = escalationResults[_disputeId]; -// if (_result.token == IERC20(address(0))) revert BondEscalationAccounting_NoEscalationResult(); -// bytes32 _requestId = _result.requestId; -// if (pledgerClaimed[_requestId][_pledger]) revert BondEscalationAccounting_AlreadyClaimed(); - -// uint256 _amountPerPledger = _result.amountPerPledger; -// uint256 _numberOfPledges = _result.forVotesWon -// ? _result.bondEscalationModule.pledgesForDispute(_requestId, _pledger) -// : _result.bondEscalationModule.pledgesAgainstDispute(_requestId, _pledger); - -// IERC20 _token = _result.token; -// uint256 _claimAmount = _amountPerPledger * _numberOfPledges; - -// pledgerClaimed[_requestId][_pledger] = true; -// balanceOf[_pledger][_token] += _claimAmount; - -// unchecked { -// pledges[_disputeId][_token] -= _claimAmount; -// } - -// emit EscalationRewardClaimed({ -// _requestId: _requestId, -// _disputeId: _disputeId, -// _pledger: _pledger, -// _token: _result.token, -// _amount: _claimAmount -// }); -// } - -// /// @inheritdoc IBondEscalationAccounting -// function releasePledge( -// bytes32 _requestId, -// bytes32 _disputeId, -// address _pledger, -// IERC20 _token, -// uint256 _amount -// ) external onlyAllowedModule(_requestId) { -// if (pledges[_disputeId][_token] < _amount) revert BondEscalationAccounting_InsufficientFunds(); - -// balanceOf[_pledger][_token] += _amount; - -// unchecked { -// pledges[_disputeId][_token] -= _amount; -// } - -// emit PledgeReleased({ -// _requestId: _requestId, -// _disputeId: _disputeId, -// _pledger: _pledger, -// _token: _token, -// _amount: _amount -// }); -// } -// } +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; + +import {AccountingExtension} from './AccountingExtension.sol'; + +import {IBondEscalationAccounting} from '../../interfaces/extensions/IBondEscalationAccounting.sol'; +import {IBondEscalationModule} from '../../interfaces/modules/dispute/IBondEscalationModule.sol'; + +contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccounting { + /// @inheritdoc IBondEscalationAccounting + mapping(bytes32 _disputeId => mapping(IERC20 _token => uint256 _amount)) public pledges; + + /// @inheritdoc IBondEscalationAccounting + mapping(bytes32 _disputeId => EscalationResult _result) public escalationResults; + + /// @inheritdoc IBondEscalationAccounting + mapping(bytes32 _requestId => mapping(address _pledger => bool)) public pledgerClaimed; + + constructor(IOracle _oracle) AccountingExtension(_oracle) {} + + /// @inheritdoc IBondEscalationAccounting + function pledge( + address _pledger, + bytes32 _requestId, + bytes32 _disputeId, + IERC20 _token, + uint256 _amount + ) external onlyAllowedModule(_requestId) { + if (balanceOf[_pledger][_token] < _amount) revert BondEscalationAccounting_InsufficientFunds(); + + pledges[_disputeId][_token] += _amount; + + unchecked { + balanceOf[_pledger][_token] -= _amount; + } + + emit Pledged({_pledger: _pledger, _requestId: _requestId, _disputeId: _disputeId, _token: _token, _amount: _amount}); + } + + /// @inheritdoc IBondEscalationAccounting + function onSettleBondEscalation( + bytes32 _requestId, + bytes32 _disputeId, + bool _forVotesWon, + IERC20 _token, + uint256 _amountPerPledger, + uint256 _winningPledgersLength + ) external onlyAllowedModule(_requestId) { + // TODO: check that flooring at _amountPerPledger calculation doesn't mess with this check + if (pledges[_disputeId][_token] < _amountPerPledger * _winningPledgersLength) { + revert BondEscalationAccounting_InsufficientFunds(); + } + + if (escalationResults[_disputeId].requestId != bytes32(0)) { + revert BondEscalationAccounting_AlreadySettled(); + } + + escalationResults[_disputeId] = EscalationResult({ + requestId: _requestId, + forVotesWon: _forVotesWon, + token: _token, + amountPerPledger: _amountPerPledger, + bondEscalationModule: IBondEscalationModule(msg.sender) + }); + + emit BondEscalationSettled({ + _requestId: _requestId, + _disputeId: _disputeId, + _forVotesWon: _forVotesWon, + _token: _token, + _amountPerPledger: _amountPerPledger, + _winningPledgersLength: _winningPledgersLength + }); + } + + /// @inheritdoc IBondEscalationAccounting + function claimEscalationReward(bytes32 _disputeId, address _pledger) external { + EscalationResult memory _result = escalationResults[_disputeId]; + if (_result.token == IERC20(address(0))) revert BondEscalationAccounting_NoEscalationResult(); + bytes32 _requestId = _result.requestId; + if (pledgerClaimed[_requestId][_pledger]) revert BondEscalationAccounting_AlreadyClaimed(); + + uint256 _amountPerPledger = _result.amountPerPledger; + uint256 _numberOfPledges = _result.forVotesWon + ? _result.bondEscalationModule.pledgesForDispute(_requestId, _pledger) + : _result.bondEscalationModule.pledgesAgainstDispute(_requestId, _pledger); + + IERC20 _token = _result.token; + uint256 _claimAmount = _amountPerPledger * _numberOfPledges; + + pledgerClaimed[_requestId][_pledger] = true; + balanceOf[_pledger][_token] += _claimAmount; + + unchecked { + pledges[_disputeId][_token] -= _claimAmount; + } + + emit EscalationRewardClaimed({ + _requestId: _requestId, + _disputeId: _disputeId, + _pledger: _pledger, + _token: _result.token, + _amount: _claimAmount + }); + } + + /// @inheritdoc IBondEscalationAccounting + function releasePledge( + bytes32 _requestId, + bytes32 _disputeId, + address _pledger, + IERC20 _token, + uint256 _amount + ) external onlyAllowedModule(_requestId) { + if (pledges[_disputeId][_token] < _amount) revert BondEscalationAccounting_InsufficientFunds(); + + balanceOf[_pledger][_token] += _amount; + + unchecked { + pledges[_disputeId][_token] -= _amount; + } + + emit PledgeReleased({ + _requestId: _requestId, + _disputeId: _disputeId, + _pledger: _pledger, + _token: _token, + _amount: _amount + }); + } +}