diff --git a/hardhat.config.ts b/hardhat.config.ts index a0917080..96fc2bbe 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -24,13 +24,16 @@ const config: HardhatUserConfig = { }, }, solidity: { - version: "0.7.5", - settings: { - optimizer: { - enabled: true, - runs: 200, + compilers: [ + { + version: "0.7.5", + settings: { optimizer: { enabled: true, runs: 200 } }, }, - }, + { + version: "0.8.26", + settings: { viaIR: true, optimizer: { enabled: true, runs: 200 } }, + }, + ] }, networks: { hardhat: { diff --git a/test/arbitrum/ArbitrumBridgeTest.t.sol b/test/arbitrum/ArbitrumBridgeTest.t.sol new file mode 100644 index 00000000..5e792f0a --- /dev/null +++ b/test/arbitrum/ArbitrumBridgeTest.t.sol @@ -0,0 +1,214 @@ +// forge test --match-path test/arbitrum/ArbitrumBridgeTest.t.sol -vvv +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test, console, console2} from "forge-std/Test.sol"; +import {IGovernorBeta} from "../interfaces/IGovernorBeta.sol"; +import {ITimelock} from "../interfaces/ITimelock.sol"; +import {ICtx} from "../interfaces/ICtx.sol"; +import {IArbitrumInboxErrors} from "../interfaces/IArbitrumInboxErrors.sol"; + +interface IPerennialCollateral { + function claimFee() external; +} + +interface IL1MessageRelayer { + function relayMessage( + address target, + bytes memory payLoad, + uint256 maxSubmissionCost, + uint256 maxGas, + uint256 gasPriceBid + ) external returns (uint256); +} + +interface IL2MessageExecutor { + function executeMessage(bytes calldata payLoad) external; +} + +interface IArbitrumTreasury { + function executeTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data + ) external payable returns (bytes memory); +} + +library AddressAliasHelper { + uint160 constant OFFSET = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Utility function that converts the address in the L1 that submitted a tx to + /// the inbox to the msg.sender viewed in the L2 + /// @param l1Address the address in the L1 that triggered the tx to L2 + /// @return l2Address L2 address as viewed in msg.sender + function applyL1ToL2Alias(address l1Address) + internal + pure + returns (address l2Address) + { + l2Address = address(uint160(l1Address) + OFFSET); + } + + /// @notice Utility function that converts the msg.sender viewed in the L2 to the + /// address in the L1 that submitted a tx to the inbox + /// @param l2Address L2 address as viewed in msg.sender + /// @return l1Address the address in the L1 that triggered the tx to L2 + function undoL1ToL2Alias(address l2Address) + internal + pure + returns (address l1Address) + { + l1Address = address(uint160(l2Address) - OFFSET); + } +} + +interface IERC20 { + function balanceOf(address account) external returns (uint256); +} + +contract AbritrumBridgeTest is Test { + ICtx ctx = ICtx(0x321C2fE4446C7c963dc41Dd58879AF648838f98D); + IGovernorBeta public governorBeta = + IGovernorBeta(0x874C5D592AfC6803c3DD60d6442357879F196d5b); + ITimelock public timelock = + ITimelock(0xa54074b2cc0e96a43048d4a68472F7F046aC0DA8); + IL1MessageRelayer l1MessageRelayer = + IL1MessageRelayer(0x209c23DB16298504354112fa4210d368e1d564dA); + // 0x3769b6aA269995297a539BEd7a463105466733A5 --> L2MessageExecutorProxy.so + IL2MessageExecutor l2MessageExecutor = + IL2MessageExecutor(0x3769b6aA269995297a539BEd7a463105466733A5); + IArbitrumTreasury arbitrumTreasury = + IArbitrumTreasury(0x9474B771Fb46E538cfED114Ca816A3e25Bb346CF); + IPerennialCollateral perennialCollateral = + IPerennialCollateral(0xAF8CeD28FcE00ABD30463D55dA81156AA5aEEEc2); + + IERC20 dsu = IERC20(0x52C64b8998eB7C80b6F526E99E29ABdcC86B841b); + + uint256 public ethereumMainnetForkId; + uint256 public arbitrumMainnetForkId; + address user = address(0x51); + + function setUp() public { + string memory ETHEREUM_MAINNET_RPC_URL = vm.envString( + "ETHEREUM_MAINNET_RPC_URL" + ); + string memory ARBITRUM_MAINNET_RPC_URL = vm.envString("ARBITRUM_API_URL"); + ethereumMainnetForkId = vm.createFork(ETHEREUM_MAINNET_RPC_URL); + arbitrumMainnetForkId = vm.createFork(ARBITRUM_MAINNET_RPC_URL); + + vm.selectFork(ethereumMainnetForkId); + deal(address(ctx), user, 900_000 ether); + vm.prank(user); + ctx.delegate(user); + } + + function createAndExecuteGovernanceProposal( + address[] memory targets, + uint256[] memory values, + string[] memory signatures, + bytes[] memory calldatas, + string memory description + ) internal { + vm.startPrank(user); + vm.roll(block.number + 100); + governorBeta.propose(targets, values, signatures, calldatas, description); + uint256 proposalID = governorBeta.latestProposalIds(user); + vm.roll(block.number + governorBeta.votingDelay() + 1); + governorBeta.castVote(proposalID, true); + vm.roll(block.number + governorBeta.votingPeriod() + 1); + governorBeta.queue(proposalID); + vm.warp(block.timestamp + timelock.delay() + 1 days); + governorBeta.execute(proposalID); + assertEq( + uint256(governorBeta.state(proposalID)), + uint256(IGovernorBeta.ProposalState.Executed) + ); + vm.stopPrank(); + } + + function test() external { + vm.selectFork(ethereumMainnetForkId); + address l2Target = address(l2MessageExecutor); + bytes memory executorPayload = abi.encode( + address(arbitrumTreasury), + abi.encodeWithSelector( + IArbitrumTreasury.executeTransaction.selector, + address(perennialCollateral), // target + 0, // value + "claimFee()", // signature + bytes("") // data + ) + ); + console2.log( + "==================================================================================" + ); + console2.log("payload to feed the script for calculating gas related data"); + console2.logBytes(executorPayload); + console2.log( + "==================================================================================" + ); + + address[] memory targets = new address[](1); + uint256[] memory values = new uint256[](1); + string[] memory signatures = new string[](1); + bytes[] memory calldatas = new bytes[](1); + + bytes memory l2CallData = abi.encodeWithSelector( + IL2MessageExecutor.executeMessage.selector, + executorPayload + ); + + targets[0] = address(l1MessageRelayer); + values[0] = 349782448131600; + signatures[0] = "relayMessage(address,bytes,uint256,uint256,uint256)"; + calldatas[0] = abi.encode( + l2Target, + l2CallData, + 344212668313600, + 111311, + 50038000 + ); + string memory description = "Claim TCAP Perp Fees"; + console2.log( + "==================================================================================" + ); + console2.log("targets[0]", targets[0]); + console2.log( + "==================================================================================" + ); + console2.log("values[0]", values[0]); + console2.log( + "==================================================================================" + ); + console2.log("signatures[0]", signatures[0]); + console2.log( + "==================================================================================" + ); + console2.log("calldatas[0]"); + console2.logBytes(calldatas[0]); + console2.log( + "==================================================================================" + ); + console2.log("description", description); + console2.log( + "==================================================================================" + ); + createAndExecuteGovernanceProposal( + targets, + values, + signatures, + calldatas, + description + ); + vm.selectFork(arbitrumMainnetForkId); + vm.startPrank( + AddressAliasHelper.applyL1ToL2Alias(address(l1MessageRelayer)) + ); + uint256 oldBalance = dsu.balanceOf(address(arbitrumTreasury)); + (bool success, ) = l2Target.call(l2CallData); + require(success, "Message call failed"); + uint256 newBalance = dsu.balanceOf(address(arbitrumTreasury)); + assertTrue((newBalance - oldBalance) > 15887 ether); + } +} diff --git a/test/ccip/GovernanceCCIPIntegrationTest.t.sol b/test/ccip/GovernanceCCIPIntegrationTest.t.sol index 6068830a..8f73e910 100644 --- a/test/ccip/GovernanceCCIPIntegrationTest.t.sol +++ b/test/ccip/GovernanceCCIPIntegrationTest.t.sol @@ -8,69 +8,9 @@ import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.s import {GovernanceCCIPRelay, IGovernanceCCIPRelay} from "contracts/ccip/GovernanceCCIPRelay.sol"; import {GovernanceCCIPReceiver, IGovernanceCCIPReceiver} from "contracts/ccip/GovernanceCCIPReceiver.sol"; import {NumberUpdater} from "contracts/mocks/NumberUpdater.sol"; - -interface IGovernorBeta { - enum ProposalState { - Pending, - Active, - Canceled, - Defeated, - Succeeded, - Queued, - Expired, - Executed - } - - function votingDelay() external pure returns (uint256); - - function votingPeriod() external pure returns (uint256); - - function state(uint256 proposalId) external view returns (ProposalState); - - function propose( - address[] memory targets, - uint256[] memory values, - string[] memory signatures, - bytes[] memory calldatas, - string memory description - ) external returns (uint256); - - function queue(uint256 proposalId) external; - - function execute(uint256 proposalId) external payable; - - function castVote(uint256 proposalId, bool support) external; - - function latestProposalIds(address) external returns (uint256); -} - -interface ITimelock { - function delay() external returns (uint256); - - function queueTransaction( - address target, - uint256 value, - string memory signature, - bytes memory data, - uint256 eta - ) external returns (bytes32); - - function executeTransaction( - address target, - uint256 value, - string memory signature, - bytes memory data, - uint256 eta - ) external payable returns (bytes memory); - - function admin() external returns (address); - - function acceptAdmin() external; -} - -interface ICtx { - function delegate(address delegatee) external; -} +import {IGovernorBeta} from "../interfaces/IGovernorBeta.sol"; +import {ITimelock} from "../interfaces/ITimelock.sol"; +import {ICtx} from "../interfaces/ICtx.sol"; interface IPriceRegistry { error StaleGasPrice( diff --git a/test/interfaces/IArbitrumInboxErrors.sol b/test/interfaces/IArbitrumInboxErrors.sol new file mode 100644 index 00000000..4a78961d --- /dev/null +++ b/test/interfaces/IArbitrumInboxErrors.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IArbitrumInboxErrors { + /// @dev Init was already called + error AlreadyInit(); + + /// @dev Init was called with param set to zero that must be nonzero + error HadZeroInit(); + + /// @dev Thrown when post upgrade init validation fails + error BadPostUpgradeInit(); + + /// @dev Thrown when the caller is not a codeless origin + error NotCodelessOrigin(); + + /// @dev Thrown when non owner tries to access an only-owner function + /// @param sender The msg.sender who is not the owner + /// @param owner The owner address + error NotOwner(address sender, address owner); + + /// @dev Thrown when an address that is not the rollup tries to call an only-rollup function + /// @param sender The sender who is not the rollup + /// @param rollup The rollup address authorized to call this function + error NotRollup(address sender, address rollup); + + /// @dev Thrown when the contract was not called directly from the origin ie msg.sender != tx.origin + error NotOrigin(); + + /// @dev Provided data was too large + /// @param dataLength The length of the data that is too large + /// @param maxDataLength The max length the data can be + error DataTooLarge(uint256 dataLength, uint256 maxDataLength); + + /// @dev The provided is not a contract and was expected to be + /// @param addr The adddress in question + error NotContract(address addr); + + /// @dev The merkle proof provided was too long + /// @param actualLength The length of the merkle proof provided + /// @param maxProofLength The max length a merkle proof can have + error MerkleProofTooLong(uint256 actualLength, uint256 maxProofLength); + + /// @dev Thrown when an un-authorized address tries to access an admin function + /// @param sender The un-authorized sender + /// @param rollup The rollup, which would be authorized + /// @param owner The rollup's owner, which would be authorized + error NotRollupOrOwner(address sender, address rollup, address owner); + + // Bridge Errors + + /// @dev Thrown when an un-authorized address tries to access an only-inbox function + /// @param sender The un-authorized sender + error NotDelayedInbox(address sender); + + /// @dev Thrown when an un-authorized address tries to access an only-sequencer-inbox function + /// @param sender The un-authorized sender + error NotSequencerInbox(address sender); + + /// @dev Thrown when an un-authorized address tries to access an only-outbox function + /// @param sender The un-authorized sender + error NotOutbox(address sender); + + /// @dev the provided outbox address isn't valid + /// @param outbox address of outbox being set + error InvalidOutboxSet(address outbox); + + /// @dev The provided token address isn't valid + /// @param token address of token being set + error InvalidTokenSet(address token); + + /// @dev Call to this specific address is not allowed + /// @param target address of the call receiver + error CallTargetNotAllowed(address target); + + /// @dev Call that changes the balance of ERC20Bridge is not allowed + error CallNotAllowed(); + + // Inbox Errors + + /// @dev msg.value sent to the inbox isn't high enough + error InsufficientValue(uint256 expected, uint256 actual); + + /// @dev submission cost provided isn't enough to create retryable ticket + error InsufficientSubmissionCost(uint256 expected, uint256 actual); + + /// @dev address not allowed to interact with the given contract + error NotAllowedOrigin(address origin); + + /// @dev used to convey retryable tx data in eth calls without requiring a tx trace + /// this follows a pattern similar to EIP-3668 where reverts surface call information + error RetryableData( + address from, + address to, + uint256 l2CallValue, + uint256 deposit, + uint256 maxSubmissionCost, + address excessFeeRefundAddress, + address callValueRefundAddress, + uint256 gasLimit, + uint256 maxFeePerGas, + bytes data + ); + + /// @dev Thrown when a L1 chainId fork is detected + error L1Forked(); + + /// @dev Thrown when a L1 chainId fork is not detected + error NotForked(); + + /// @dev The provided gasLimit is larger than uint64 + error GasLimitTooLarge(); + + /// @dev The provided amount cannot be adjusted to 18 decimals due to overflow + error AmountTooLarge(uint256 amount); + + /// @dev Number of native token's decimals is restricted to enable conversions to 18 decimals + error NativeTokenDecimalsTooLarge(uint256 decimals); + + // Outbox Errors + + /// @dev The provided proof was too long + /// @param proofLength The length of the too-long proof + error ProofTooLong(uint256 proofLength); + + /// @dev The output index was greater than the maximum + /// @param index The output index + /// @param maxIndex The max the index could be + error PathNotMinimal(uint256 index, uint256 maxIndex); + + /// @dev The calculated root does not exist + /// @param root The calculated root + error UnknownRoot(bytes32 root); + + /// @dev The record has already been spent + /// @param index The index of the spent record + error AlreadySpent(uint256 index); + + /// @dev A call to the bridge failed with no return data + error BridgeCallFailed(); + + // Sequencer Inbox Errors + + /// @dev Thrown when someone attempts to read fewer messages than have already been read + error DelayedBackwards(); + + /// @dev Thrown when someone attempts to read more messages than exist + error DelayedTooFar(); + + /// @dev Force include can only read messages more blocks old than the delay period + error ForceIncludeBlockTooSoon(); + + /// @dev The message provided did not match the hash in the delayed inbox + error IncorrectMessagePreimage(); + + /// @dev This can only be called by the batch poster + error NotBatchPoster(); + + /// @dev The sequence number provided to this message was inconsistent with the number of batches already included + error BadSequencerNumber(uint256 stored, uint256 received); + + /// @dev The sequence message number provided to this message was inconsistent with the previous one + error BadSequencerMessageNumber(uint256 stored, uint256 received); + + /// @dev Tried to create an already valid Data Availability Service keyset + error AlreadyValidDASKeyset(bytes32); + + /// @dev Tried to use or invalidate an already invalid Data Availability Service keyset + error NoSuchKeyset(bytes32); + + /// @dev Thrown when the provided address is not the designated batch poster manager + error NotBatchPosterManager(address); + + /// @dev Thrown when a data blob feature is attempted to be used on a chain that doesnt support it + error DataBlobsNotSupported(); + + /// @dev Thrown when batches are posted without buffer proof, this is only allowed in a sync state or when no new delayed messages are read + error DelayProofRequired(); + + /// @dev The DelayedAccPreimage is invalid + error InvalidDelayedAccPreimage(); + + /// @dev Thrown when the sequencer attempts to post a batch with delay / sync proofs without delay bufferability enabled + error NotDelayBufferable(); + + /// @dev Thrown when an init param was supplied as empty + error InitParamZero(string name); + + /// @dev Thrown when data hashes where expected but not where present on the tx + error MissingDataHashes(); + + /// @dev Thrown when rollup is not updated with updateRollupAddress + error RollupNotChanged(); + + /// @dev Unsupported header flag was provided + error InvalidHeaderFlag(bytes1); + + /// @dev SequencerInbox and Bridge are not in the same feeToken/ETH mode + error NativeTokenMismatch(); + + /// @dev Thrown when a deprecated function is called + error Deprecated(); + + /// @dev Thrown when any component of maxTimeVariation is over uint64 + error BadMaxTimeVariation(); + + /// @dev Thrown when any component of bufferConfig is zero + error BadBufferConfig(); + + /// @dev Thrown when extra gas is not a uint64 + error ExtraGasNotUint64(); + + /// @dev Thrown when keysetBytes is too large + error KeysetTooLarge(); +} diff --git a/test/interfaces/ICtx.sol b/test/interfaces/ICtx.sol new file mode 100644 index 00000000..f016892f --- /dev/null +++ b/test/interfaces/ICtx.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ICtx { + // Events + event MinterChanged(address minter, address newMinter); + event DelegateChanged( + address indexed delegator, + address indexed fromDelegate, + address indexed toDelegate + ); + event DelegateVotesChanged( + address indexed delegate, + uint256 previousBalance, + uint256 newBalance + ); + event Transfer(address indexed from, address indexed to, uint256 amount); + event Approval( + address indexed owner, + address indexed spender, + uint256 amount + ); + + // View Functions + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function decimals() external view returns (uint8); + + function totalSupply() external view returns (uint256); + + function minter() external view returns (address); + + function mintingAllowedAfter() external view returns (uint256); + + function balanceOf(address account) external view returns (uint256); + + function allowance(address account, address spender) + external + view + returns (uint256); + + function getCurrentVotes(address account) external view returns (uint96); + + function getPriorVotes(address account, uint256 blockNumber) + external + view + returns (uint96); + + function delegates(address account) external view returns (address); + + function nonces(address account) external view returns (uint256); + + // State-Changing Functions + function setMinter(address minter_) external; + + function mint(address dst, uint256 rawAmount) external; + + function transfer(address dst, uint256 rawAmount) external returns (bool); + + function transferFrom( + address src, + address dst, + uint256 rawAmount + ) external returns (bool); + + function approve(address spender, uint256 rawAmount) external returns (bool); + + function increaseAllowance(address spender, uint256 addedValue) + external + returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) + external + returns (bool); + + function delegate(address delegatee) external; + + function delegateBySig( + address delegatee, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + function permit( + address owner, + address spender, + uint256 rawAmount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} diff --git a/test/interfaces/IGovernorBeta.sol b/test/interfaces/IGovernorBeta.sol new file mode 100644 index 00000000..9999bc80 --- /dev/null +++ b/test/interfaces/IGovernorBeta.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IGovernorBeta { + enum ProposalState { + Pending, + Active, + Canceled, + Defeated, + Succeeded, + Queued, + Expired, + Executed + } + + function votingDelay() external pure returns (uint256); + + function votingPeriod() external pure returns (uint256); + + function state(uint256 proposalId) external view returns (ProposalState); + + function propose( + address[] memory targets, + uint256[] memory values, + string[] memory signatures, + bytes[] memory calldatas, + string memory description + ) external returns (uint256); + + function queue(uint256 proposalId) external; + + function execute(uint256 proposalId) external payable; + + function castVote(uint256 proposalId, bool support) external; + + function latestProposalIds(address) external returns (uint256); +} diff --git a/test/interfaces/ITimelock.sol b/test/interfaces/ITimelock.sol new file mode 100644 index 00000000..e6ab5769 --- /dev/null +++ b/test/interfaces/ITimelock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ITimelock { + function delay() external returns (uint256); + + function queueTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) external returns (bytes32); + + function executeTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) external payable returns (bytes memory); + + function admin() external returns (address); + + function acceptAdmin() external; +}