diff --git a/README.md b/README.md index e63c749..7acc53e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Proposal title +# Proposal to compensate relayer attack losses ### Changes / effect description @@ -10,15 +10,14 @@ - Rust ([Need only for Windows](https://doc.rust-lang.org/cargo/getting-started/installation.html)) - Foundryup ([Windows](https://github.com/altugbakan/foundryup-windows), [Linux](https://book.getfoundry.sh/getting-started/installation)) -- Node 14 or higher ([Windows](https://github.com/coreybutler/nvm-windows), [Linux](https://github.com/nvm-sh/nvm)) +- Node 18 or higher ([Windows](https://github.com/coreybutler/nvm-windows), [Linux](https://github.com/nvm-sh/nvm)) ### Installation ```text -git clone --recurse-submodules -cd +git clone --recurse-submodules https://git.tornado.ws/Theo/proposal-33-compensate-relayer-losses +cd proposal-33-compensate-relayer-losses npm install -npm run init ``` ### Testing diff --git a/data/.gitkeep b/data/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..f73c73d --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 diff --git a/package.json b/package.json index 371e4ec..5ae0681 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@openzeppelin/upgrades-core": "^1.26.2", "axios": "^1.5.1", "dotenv": "^16.3.1", + "env-cmd": "^10.1.0", "web3": "^4.2.0", "web3-utils": "^4.0.7" }, diff --git a/scripts/calculateLosses.ts b/scripts/calculateLosses.ts index 2c9de97..2264859 100644 --- a/scripts/calculateLosses.ts +++ b/scripts/calculateLosses.ts @@ -15,11 +15,7 @@ async function fetchFailedWithdrawals() { let failedWithdrawalTransactions = []; for (let blockNumber = attackStartBlock; blockNumber <= attackEndBlock; blockNumber++) { - let block = null; - while (block == null) - try { - block = await web3.eth.getBlock(blockNumber); - } catch (e) {} + const block = await web3.eth.getBlock(blockNumber); const results = await Promise.allSettled(block.transactions.map((tx) => web3.eth.getTransactionReceipt(tx as string))); const transactions = results.filter((r) => r.status === "fulfilled").map((r) => (r as PromiseFulfilledResult).value); failedWithdrawalTransactions.push(...transactions.filter((tx) => tx.to === routerAddress.toLowerCase() && tx.status == 0)); diff --git a/src/ExampleProposal.sol b/src/ExampleProposal.sol deleted file mode 100644 index 68fd33d..0000000 --- a/src/ExampleProposal.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.19; - -import { IGovernance } from "@interfaces/IGovernance.sol"; - -contract ExampleProposal { - function executeProposal() public { - /* ... */ - } -} diff --git a/src/RelayerCompensationProposal.sol b/src/RelayerCompensationProposal.sol new file mode 100644 index 0000000..3dd5933 --- /dev/null +++ b/src/RelayerCompensationProposal.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +import { IGovernance } from "@interfaces/IGovernance.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract RelayerCompensationProposal { + IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C); + + struct RelayerLoss { + address relayer; + uint256 lostAmountInTorn; + } + + function executeProposal() public { + // Data from 'data/relayerLosses.txt', generated by 'scripts/calculateLosses.ts' + RelayerLoss[4] memory relayerLosses = [ + RelayerLoss(0x864DF9CD806D58341f13602103Bf853066ff962a, 625_894_225_496_155_734_516), + RelayerLoss(0x5555555731006f71f121144534Ca7C8799F66AA3, 43_970_301_082_908_267_318), + RelayerLoss(0x2Ee39Ff05643bC7cc9ed31B71e142429044A425C, 189_640_345_451_160_934_437), + RelayerLoss(0x03392600086874456E08D2bAc104380BCdEBCfC0, 129_757_783_603_193_410_350) + ]; + + for (uint256 i = 0; i < relayerLosses.length; i++) { + TORN.transfer(relayerLosses[i].relayer, relayerLosses[i].lostAmountInTorn); + } + } +} diff --git a/test/ExampleProposal.t.sol b/test/ExampleProposal.t.sol deleted file mode 100644 index 9efd7bc..0000000 --- a/test/ExampleProposal.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import { ProposalUtils } from "./utils/ProposalUtils.sol"; -import { ExampleProposal } from "@root/ExampleProposal.sol"; - -import { console2 } from "@forge-std/console2.sol"; - -contract TestExampleProposal is ProposalUtils { - modifier executeCurrentProposalBefore() { - createAndExecuteProposal(); - _; - } - - function createAndExecuteProposal() public { - address proposalAddress = address(new ExampleProposal()); /* your proposal initialization */ - - proposeAndExecute(proposalAddress); - } - - /* your tests */ - - function testProposal() public executeCurrentProposalBefore { } -} diff --git a/test/RelayerCompensationProposal.t.sol b/test/RelayerCompensationProposal.t.sol new file mode 100644 index 0000000..095680e --- /dev/null +++ b/test/RelayerCompensationProposal.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { ProposalUtils } from "./utils/ProposalUtils.sol"; +import { RelayerCompensationProposal } from "@root/RelayerCompensationProposal.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { console2 } from "@forge-std/console2.sol"; + +contract TestExampleProposal is ProposalUtils, RelayerCompensationProposal { + modifier executeCurrentProposalBefore() { + createAndExecuteProposal(); + _; + } + + function createAndExecuteProposal() public { + address proposalAddress = address(new RelayerCompensationProposal()); + + proposeAndExecute(proposalAddress); + } + + function getRelayerLosses() internal pure returns (RelayerLoss[4] memory) { + return [ + RelayerLoss(0x864DF9CD806D58341f13602103Bf853066ff962a, 625_894_225_496_155_734_516), + RelayerLoss(0x5555555731006f71f121144534Ca7C8799F66AA3, 43_970_301_082_908_267_318), + RelayerLoss(0x2Ee39Ff05643bC7cc9ed31B71e142429044A425C, 189_640_345_451_160_934_437), + RelayerLoss(0x03392600086874456E08D2bAc104380BCdEBCfC0, 129_757_783_603_193_410_350) + ]; + } + + function testRelayerGotCompensation() public { + RelayerLoss[4] memory relayerLosses = getRelayerLosses(); + + uint256[4] memory balancesBeforeProposal; + for (uint256 i = 0; i < relayerLosses.length; i++) { + balancesBeforeProposal[i] = TORN.balanceOf(relayerLosses[i].relayer); + } + + createAndExecuteProposal(); + + for (uint256 i = 0; i < relayerLosses.length; i++) { + uint256 updatedBalance = TORN.balanceOf(relayerLosses[i].relayer); + require(balancesBeforeProposal[i] + relayerLosses[i].lostAmountInTorn == updatedBalance, "Compensation failed"); + } + } + + function testLossesDataIsValid() public pure { + RelayerLoss[4] memory relayerLosses = getRelayerLosses(); + uint256 tornDecimals = 1e18; + + require(relayerLosses[0].lostAmountInTorn / tornDecimals == 625); + require(relayerLosses[1].lostAmountInTorn / tornDecimals == 43); + require(relayerLosses[2].lostAmountInTorn / tornDecimals == 189); + require(relayerLosses[3].lostAmountInTorn / tornDecimals == 129); + } +}