diff --git a/contracts/makefile b/contracts/makefile index 84882981f1..99f67bb86b 100644 --- a/contracts/makefile +++ b/contracts/makefile @@ -99,7 +99,7 @@ verify-blockscout-base-sepolia :; deploy-sepolia :; @echo "Deploying $(contract)..." - @SAVE_DEPLOYMENTS=1 forge script scripts/deployments/$(if $(findstring facet,$(type)),facets/)${contract}.s.sol:${contract} --ffi --rpc-url sepolia --private-key ${TESTNET_PRIVATE_KEY} --broadcast --verify -vvvv + @SAVE_DEPLOYMENTS=1 forge script scripts/deployments/$(if $(findstring facet,$(type)),facets/)${contract}.s.sol:${contract} --ffi --rpc-url sepolia --private-key ${TESTNET_PRIVATE_KEY} --broadcast --verify --legacy -vvvv verify-etherscan-sepolia :; @forge verify-contract ${address} ${contract} --chain sepolia --verifier-url ${ETHERSCAN_SEPOLIA_URL} --etherscan-api-key ${ETHERSCAN_API_KEY} @@ -147,7 +147,7 @@ verify-blockscout-base :; # =========================== deploy-mainnet :; - @SAVE_DEPLOYMENTS=1 forge script scripts/deployments/$(if $(findstring facet,$(type)),facets/)${contract}.s.sol:${contract} --ffi --rpc-url mainnet --private-key ${TESTNET_PRIVATE_KEY} --broadcast --verify -vvvv + @SAVE_DEPLOYMENTS=1 forge script scripts/deployments/$(if $(findstring facet,$(type)),facets/)${contract}.s.sol:${contract} --ledger --hd-paths "m/44'/60'/1'/0/0" --sender ${PRODUCTION_ADDRESS} --ffi --rpc-url mainnet --broadcast --verify --legacy -vvvv verify-mainnet :; @forge verify-contract ${address} ${contract} --chain mainnet diff --git a/contracts/scripts/deployments/DeployProxyDelegation.s.sol b/contracts/scripts/deployments/DeployProxyBatchDelegation.s.sol similarity index 79% rename from contracts/scripts/deployments/DeployProxyDelegation.s.sol rename to contracts/scripts/deployments/DeployProxyBatchDelegation.s.sol index 1c1da94c21..259777c9d5 100644 --- a/contracts/scripts/deployments/DeployProxyDelegation.s.sol +++ b/contracts/scripts/deployments/DeployProxyBatchDelegation.s.sol @@ -7,24 +7,20 @@ pragma solidity ^0.8.23; //contracts import {Deployer} from "../common/Deployer.s.sol"; -import {ProxyDelegation} from "contracts/src/tokens/river/mainnet/delegation/ProxyDelegation.sol"; +import {ProxyBatchDelegation} from "contracts/src/tokens/river/mainnet/delegation/ProxyBatchDelegation.sol"; // deployments import {DeployRiverMainnet} from "./DeployRiverMainnet.s.sol"; import {DeployAuthorizedClaimers} from "./DeployAuthorizedClaimers.s.sol"; -import {DeployBaseRegistry} from "contracts/scripts/deployments/DeployBaseRegistry.s.sol"; import {MockMessenger} from "contracts/test/mocks/MockMessenger.sol"; -contract DeployProxyDelegation is Deployer { +contract DeployProxyBatchDelegation is Deployer { // Mainnet DeployRiverMainnet internal riverHelper = new DeployRiverMainnet(); DeployAuthorizedClaimers internal claimersHelper = new DeployAuthorizedClaimers(); - // Base - DeployBaseRegistry internal deployBaseRegistry = new DeployBaseRegistry(); - address public riverToken; address public claimers; address public mainnetDelegation; @@ -32,7 +28,7 @@ contract DeployProxyDelegation is Deployer { address public vault; function versionName() public pure override returns (string memory) { - return "proxyDelegation"; + return "proxyBatchDelegation"; } function setDependencies( @@ -53,7 +49,7 @@ contract DeployProxyDelegation is Deployer { vm.broadcast(deployer); messenger = address(new MockMessenger()); } else { - messenger = _getMessenger(); + messenger = getMessenger(); } } @@ -64,7 +60,12 @@ contract DeployProxyDelegation is Deployer { vm.broadcast(deployer); return address( - new ProxyDelegation(riverToken, claimers, messenger, mainnetDelegation) + new ProxyBatchDelegation( + riverToken, + claimers, + messenger, + mainnetDelegation + ) ); } @@ -74,10 +75,15 @@ contract DeployProxyDelegation is Deployer { return 0x08cC41b782F27d62995056a4EF2fCBAe0d3c266F; } + if (block.chainid == 1) { + // Base Registry contract on Base + return 0x7c0422b31401C936172C897802CF0373B35B7698; + } + return getDeployment("baseRegistry"); } - function _getMessenger() internal view returns (address) { + function getMessenger() public view returns (address) { // Base or Base (Sepolia) if (block.chainid == 8453 || block.chainid == 84532) { return 0x4200000000000000000000000000000000000007; diff --git a/contracts/scripts/deployments/facets/DeployMainnetDelegation.s.sol b/contracts/scripts/deployments/facets/DeployMainnetDelegation.s.sol index e35d9c32f8..6c2f17f7d6 100644 --- a/contracts/scripts/deployments/facets/DeployMainnetDelegation.s.sol +++ b/contracts/scripts/deployments/facets/DeployMainnetDelegation.s.sol @@ -19,6 +19,9 @@ contract DeployMainnetDelegation is FacetHelper, Deployer { addSelector(MainnetDelegation.getDelegatedStakeByOperator.selector); addSelector(MainnetDelegation.setAuthorizedClaimer.selector); addSelector(MainnetDelegation.getAuthorizedClaimer.selector); + addSelector(MainnetDelegation.setBatchDelegation.selector); + addSelector(MainnetDelegation.getProxyDelegation.selector); + addSelector(MainnetDelegation.getMessenger.selector); } function initializer() public pure override returns (bytes4) { diff --git a/contracts/scripts/interactions/InteractPostDeploy.s.sol b/contracts/scripts/interactions/InteractPostDeploy.s.sol index a771d32490..2737427235 100644 --- a/contracts/scripts/interactions/InteractPostDeploy.s.sol +++ b/contracts/scripts/interactions/InteractPostDeploy.s.sol @@ -17,27 +17,28 @@ import {DeploySpaceOwner} from "contracts/scripts/deployments/DeploySpaceOwner.s import {DeploySpaceFactory} from "contracts/scripts/deployments/DeploySpaceFactory.s.sol"; import {DeployBaseRegistry} from "contracts/scripts/deployments/DeployBaseRegistry.s.sol"; import {DeployRiverBase} from "contracts/scripts/deployments/DeployRiverBase.s.sol"; -import {DeployProxyDelegation} from "contracts/scripts/deployments/DeployProxyDelegation.s.sol"; +import {DeployProxyBatchDelegation} from "contracts/scripts/deployments/DeployProxyBatchDelegation.s.sol"; contract InteractPostDeploy is Interaction { DeploySpaceOwner deploySpaceOwner = new DeploySpaceOwner(); DeploySpaceFactory deploySpaceFactory = new DeploySpaceFactory(); DeployBaseRegistry deployBaseRegistry = new DeployBaseRegistry(); DeployRiverBase deployRiverBaseToken = new DeployRiverBase(); - DeployProxyDelegation deployProxyDelegation = new DeployProxyDelegation(); + DeployProxyBatchDelegation deployProxyDelegation = + new DeployProxyBatchDelegation(); function __interact(address deployer) public override { address spaceOwner = deploySpaceOwner.deploy(); address spaceFactory = deploySpaceFactory.deploy(); address baseRegistry = deployBaseRegistry.deploy(); address riverBaseToken = deployRiverBaseToken.deploy(); - // address mainnetProxyDelegation = deployProxyDelegation.deploy(); + address mainnetProxyDelegation = deployProxyDelegation.deploy(); vm.startBroadcast(deployer); ISpaceOwner(spaceOwner).setFactory(spaceFactory); IImplementationRegistry(spaceFactory).addImplementation(baseRegistry); SpaceDelegationFacet(baseRegistry).setRiverToken(riverBaseToken); - // IMainnetDelegation(baseRegistry).setProxyDelegation(mainnetProxyDelegation); + IMainnetDelegation(baseRegistry).setProxyDelegation(mainnetProxyDelegation); vm.stopBroadcast(); } } diff --git a/contracts/src/tokens/river/base/delegation/IMainnetDelegation.sol b/contracts/src/tokens/river/base/delegation/IMainnetDelegation.sol index f37850f04e..b03e322956 100644 --- a/contracts/src/tokens/river/base/delegation/IMainnetDelegation.sol +++ b/contracts/src/tokens/river/base/delegation/IMainnetDelegation.sol @@ -45,6 +45,20 @@ interface IMainnetDelegationBase { } interface IMainnetDelegation is IMainnetDelegationBase { + /** + * @notice Set batch cross-chain delegation + * @param delegators The delegator address + * @param delegates The address the delegator is delegating to + * @param claimers The address the delegator is allowing to claim + * @param quantities The quantity to delegate + */ + function setBatchDelegation( + address[] calldata delegators, + address[] calldata delegates, + address[] calldata claimers, + uint256[] calldata quantities + ) external; + /** * @notice Set delegation of a delegator to a operator * @param delegator The delegator address diff --git a/contracts/src/tokens/river/base/delegation/MainnetDelegation.sol b/contracts/src/tokens/river/base/delegation/MainnetDelegation.sol index 3b4721c068..a94b3a0943 100644 --- a/contracts/src/tokens/river/base/delegation/MainnetDelegation.sol +++ b/contracts/src/tokens/river/base/delegation/MainnetDelegation.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.23; // interfaces import {IMainnetDelegation} from "contracts/src/tokens/river/base/delegation/IMainnetDelegation.sol"; import {ICrossDomainMessenger} from "contracts/src/tokens/river/mainnet/delegation/ICrossDomainMessenger.sol"; -import {IProxyDelegation} from "contracts/src/tokens/river/mainnet/delegation/IProxyDelegation.sol"; // libraries @@ -48,11 +47,41 @@ contract MainnetDelegation is } // ============================================================= - // Delegation + // Getters + // ============================================================= + function getMessenger() external view returns (address) { + return address(_getMessenger()); + } + + function getProxyDelegation() external view returns (address) { + return address(_getProxyDelegation()); + } + // ============================================================= + // Batch Delegation + // ============================================================= + function setBatchDelegation( + address[] calldata delegators, + address[] calldata delegates, + address[] calldata claimers, + uint256[] calldata quantities + ) external onlyCrossDomainMessenger { + uint256 delegatorsLen = delegators.length; + for (uint256 i; i < delegatorsLen; i++) { + _replaceDelegation( + delegators[i], + claimers[i], + delegates[i], + quantities[i] + ); + } + } + // ============================================================= + // Delegation + // ============================================================= function setProxyDelegation(address proxyDelegation) external onlyOwner { - _setProxyDelegation(IProxyDelegation(proxyDelegation)); + _setProxyDelegation(proxyDelegation); } /// @inheritdoc IMainnetDelegation diff --git a/contracts/src/tokens/river/base/delegation/MainnetDelegationBase.sol b/contracts/src/tokens/river/base/delegation/MainnetDelegationBase.sol index cb8b333848..e70587b18c 100644 --- a/contracts/src/tokens/river/base/delegation/MainnetDelegationBase.sol +++ b/contracts/src/tokens/river/base/delegation/MainnetDelegationBase.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.23; // interfaces import {IMainnetDelegationBase} from "./IMainnetDelegation.sol"; -import {IProxyDelegation} from "contracts/src/tokens/river/mainnet/delegation/IProxyDelegation.sol"; import {ICrossDomainMessenger} from "contracts/src/tokens/river/mainnet/delegation/ICrossDomainMessenger.sol"; // libraries @@ -15,6 +14,47 @@ import {MainnetDelegationStorage} from "./MainnetDelegationStorage.sol"; abstract contract MainnetDelegationBase is IMainnetDelegationBase { using EnumerableSet for EnumerableSet.AddressSet; + function _replaceDelegation( + address delegator, + address claimer, + address operator, + uint256 quantity + ) internal { + MainnetDelegationStorage.Layout storage ds = MainnetDelegationStorage + .layout(); + Delegation storage delegation = ds.delegationByDelegator[delegator]; + address currentClaimer = ds.claimerByDelegator[delegator]; + + // Remove the current delegation if it exists + if (delegation.operator != address(0)) { + ds.delegatorsByOperator[delegation.operator].remove(delegator); + + if ( + ds.delegatorsByAuthorizedClaimer[currentClaimer].contains(delegator) + ) { + ds.delegatorsByAuthorizedClaimer[currentClaimer].remove(delegator); + } + } + + // Set the new delegation + ds.delegatorsByOperator[operator].add(delegator); + ds.delegationByDelegator[delegator] = Delegation( + operator, + quantity, + delegator, + block.timestamp + ); + + // Update the claimer if it has changed + if (claimer != currentClaimer) { + if (currentClaimer != address(0)) { + ds.delegatorsByAuthorizedClaimer[currentClaimer].remove(delegator); + } + ds.claimerByDelegator[delegator] = claimer; + ds.delegatorsByAuthorizedClaimer[claimer].add(delegator); + } + } + function _setDelegation( address delegator, address operator, @@ -110,11 +150,11 @@ abstract contract MainnetDelegationBase is IMainnetDelegationBase { return MainnetDelegationStorage.layout().claimerByDelegator[owner]; } - function _setProxyDelegation(IProxyDelegation proxyDelegation) internal { + function _setProxyDelegation(address proxyDelegation) internal { MainnetDelegationStorage.layout().proxyDelegation = proxyDelegation; } - function _getProxyDelegation() internal view returns (IProxyDelegation) { + function _getProxyDelegation() internal view returns (address) { return MainnetDelegationStorage.layout().proxyDelegation; } diff --git a/contracts/src/tokens/river/base/delegation/MainnetDelegationStorage.sol b/contracts/src/tokens/river/base/delegation/MainnetDelegationStorage.sol index 443ebfcfa2..4e25561c33 100644 --- a/contracts/src/tokens/river/base/delegation/MainnetDelegationStorage.sol +++ b/contracts/src/tokens/river/base/delegation/MainnetDelegationStorage.sol @@ -20,9 +20,10 @@ library MainnetDelegationStorage { mapping(address operator => EnumerableSet.AddressSet) delegatorsByOperator; mapping(address delegator => IMainnetDelegationBase.Delegation delegation) delegationByDelegator; mapping(address delegator => address claimer) claimerByDelegator; - IProxyDelegation proxyDelegation; + IProxyDelegation deprecatedproxyDelegation; // Do not use this, use proxyDelegation ICrossDomainMessenger messenger; mapping(address claimer => EnumerableSet.AddressSet delegators) delegatorsByAuthorizedClaimer; + address proxyDelegation; } function layout() internal pure returns (Layout storage s) { diff --git a/contracts/src/tokens/river/mainnet/delegation/ICrossDomainMessenger.sol b/contracts/src/tokens/river/mainnet/delegation/ICrossDomainMessenger.sol index db8e4a29c5..a9f7d510c6 100644 --- a/contracts/src/tokens/river/mainnet/delegation/ICrossDomainMessenger.sol +++ b/contracts/src/tokens/river/mainnet/delegation/ICrossDomainMessenger.sol @@ -8,11 +8,57 @@ pragma solidity ^0.8.23; // contracts interface ICrossDomainMessenger { - function xDomainMessageSender() external view returns (address); + event FailedRelayedMessage(bytes32 indexed msgHash); + event RelayedMessage(bytes32 indexed msgHash); + event SentMessage( + address indexed target, + address sender, + bytes message, + uint256 messageNonce, + uint256 gasLimit + ); + event SentMessageExtension1(address indexed sender, uint256 value); + + function MESSAGE_VERSION() external view returns (uint16); + + function MIN_GAS_CALLDATA_OVERHEAD() external view returns (uint64); + + function MIN_GAS_CONSTANT_OVERHEAD() external view returns (uint64); + + function MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR() + external + view + returns (uint64); + + function MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR() external view returns (uint64); + + function OTHER_MESSENGER() external view returns (address); + + function baseGas( + bytes memory _message, + uint32 _minGasLimit + ) external pure returns (uint64); + + function failedMessages(bytes32) external view returns (bool); + + function messageNonce() external view returns (uint256); + + function relayMessage( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _minGasLimit, + bytes memory _message + ) external payable; function sendMessage( - address target, - bytes calldata message, - uint32 gasLimit - ) external; + address _target, + bytes memory _message, + uint32 _minGasLimit + ) external payable; + + function successfulMessages(bytes32) external view returns (bool); + + function xDomainMessageSender() external view returns (address); } diff --git a/contracts/src/tokens/river/mainnet/delegation/IProxyBatchDelegation.sol b/contracts/src/tokens/river/mainnet/delegation/IProxyBatchDelegation.sol new file mode 100644 index 0000000000..fd68bd747e --- /dev/null +++ b/contracts/src/tokens/river/mainnet/delegation/IProxyBatchDelegation.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +// interfaces + +// libraries + +// contracts + +interface IProxyBatchDelegation { + function sendDelegators() external; +} diff --git a/contracts/src/tokens/river/mainnet/delegation/ProxyBatchDelegation.sol b/contracts/src/tokens/river/mainnet/delegation/ProxyBatchDelegation.sol new file mode 100644 index 0000000000..3ae00d366f --- /dev/null +++ b/contracts/src/tokens/river/mainnet/delegation/ProxyBatchDelegation.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.23; + +// interfaces +import {ICrossDomainMessenger} from "./ICrossDomainMessenger.sol"; +import {IMainnetDelegation} from "contracts/src/tokens/river/base/delegation/IMainnetDelegation.sol"; +import {IProxyBatchDelegation} from "./IProxyBatchDelegation.sol"; + +// libraries + +// contracts +import {River} from "contracts/src/tokens/river/mainnet/River.sol"; +import {AuthorizedClaimers} from "contracts/src/tokens/river/mainnet/claimer/AuthorizedClaimers.sol"; + +contract ProxyBatchDelegation is IProxyBatchDelegation { + address public immutable MESSENGER; + address public immutable TARGET; + + River internal immutable rvr; + AuthorizedClaimers internal immutable claimers; + + constructor( + address _rvr, + address _claimers, + address _messenger, + address _target + ) { + rvr = River(_rvr); + claimers = AuthorizedClaimers(_claimers); + + MESSENGER = _messenger; + TARGET = _target; + } + + function sendDelegators() external { + address[] memory delegators = rvr.getDelegators(); + address[] memory delegates = new address[](delegators.length); + address[] memory authorizedClaimers = new address[](delegators.length); + uint256[] memory quantities = new uint256[](delegators.length); + + for (uint256 i = 0; i < delegators.length; i++) { + authorizedClaimers[i] = claimers.getAuthorizedClaimer(delegators[i]); + delegates[i] = rvr.delegates(delegators[i]); + quantities[i] = rvr.balanceOf(delegators[i]); + } + + _sendMessage( + abi.encodeWithSelector( + IMainnetDelegation.setBatchDelegation.selector, + delegators, + delegates, + authorizedClaimers, + quantities + ) + ); + } + + function _sendMessage(bytes memory data) internal { + ICrossDomainMessenger(MESSENGER).sendMessage(TARGET, data, 400_000); + } +} diff --git a/contracts/src/tokens/river/mainnet/delegation/ProxyDelegation.sol b/contracts/src/tokens/river/mainnet/delegation/ProxyDelegation.sol deleted file mode 100644 index 7f64fcf798..0000000000 --- a/contracts/src/tokens/river/mainnet/delegation/ProxyDelegation.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -// interfaces -import {IProxyDelegation} from "./IProxyDelegation.sol"; -import {ICrossDomainMessenger} from "./ICrossDomainMessenger.sol"; -import {IMainnetDelegation} from "contracts/src/tokens/river/base/delegation/IMainnetDelegation.sol"; - -// libraries -import {EIP712} from "contracts/src/diamond/utils/cryptography/EIP712.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; - -// contracts -import {River} from "contracts/src/tokens/river/mainnet/River.sol"; -import {AuthorizedClaimers} from "contracts/src/tokens/river/mainnet/claimer/AuthorizedClaimers.sol"; - -// Chain: Mainnet -contract ProxyDelegation is IProxyDelegation, EIP712 { - address public immutable MESSENGER; - address public immutable TARGET; - - River internal immutable rvr; - AuthorizedClaimers internal immutable claimers; - - bytes32 private constant _DELEGATION_TYPEHASH = - keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); - - constructor( - address _rvr, - address _claimers, - address _messenger, - address _target - ) { - rvr = River(_rvr); - claimers = AuthorizedClaimers(_claimers); - - MESSENGER = _messenger; - TARGET = _target; - } - - function delegateBySig( - address delegatee, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) public { - rvr.delegateBySig(delegatee, nonce, expiry, v, r, s); - - bytes32 domainSeparator = rvr.DOMAIN_SEPARATOR(); - bytes32 structHash = MessageHashUtils.toTypedDataHash( - domainSeparator, - keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry)) - ); - address delegator = ECDSA.recover(structHash, v, r, s); - uint256 balance = rvr.balanceOf(delegator); - - _sendMessage( - abi.encodeWithSelector( - IMainnetDelegation.setDelegation.selector, - delegator, - delegatee, - balance - ) - ); - } - - function authorizeBySig( - address owner, - address claimer, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) public { - claimers.authorizeClaimerBySig(owner, claimer, nonce, expiry, v, r, s); - - _sendMessage( - abi.encodeWithSelector( - IMainnetDelegation.setAuthorizedClaimer.selector, - owner, - claimer - ) - ); - } - - // ============================================================= - // Internal - // ============================================================= - function _sendMessage(bytes memory data) internal { - ICrossDomainMessenger(MESSENGER).sendMessage(TARGET, data, 50_000); - } -} diff --git a/contracts/src/tokens/river/mainnet/delegation/ProxyDelegationStorage.sol b/contracts/src/tokens/river/mainnet/delegation/ProxyDelegationStorage.sol deleted file mode 100644 index 486079c16e..0000000000 --- a/contracts/src/tokens/river/mainnet/delegation/ProxyDelegationStorage.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -// interfaces - -// libraries - -// contracts - -library ProxyDelegationStorage { - // keccak256(abi.encode(uint256(keccak256("river.mainnet.delegation.storage")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 internal constant STORAGE_SLOT = - 0xc95eced7d596f82215fa5f7f46b3d150b8697bffebf6365a405c21281c2c9e00; - - struct Layout { - address rvr; - mapping(address => address) authorizedClaimers; - } - - function layout() internal pure returns (Layout storage l) { - bytes32 slot = STORAGE_SLOT; - assembly { - l.slot := slot - } - } -} diff --git a/contracts/test/fork/ForkPlayground.t.sol b/contracts/test/fork/ForkPlayground.t.sol new file mode 100644 index 0000000000..a4835167de --- /dev/null +++ b/contracts/test/fork/ForkPlayground.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +// utils +import {TestUtils} from "contracts/test/utils/TestUtils.sol"; + +//interfaces + +//libraries + +//contracts +import {DeployProxyBatchDelegation} from "contracts/scripts/deployments/DeployProxyBatchDelegation.s.sol"; +import {ProxyBatchDelegation} from "contracts/src/tokens/river/mainnet/delegation/ProxyBatchDelegation.sol"; + +import {MockMessenger} from "contracts/test/mocks/MockMessenger.sol"; +import {MainnetDelegation} from "contracts/src/tokens/river/base/delegation/MainnetDelegation.sol"; + +contract ForkPlaygroundTest is TestUtils { + // Base Registry on Base + address baseRegistryAddress = 0x7c0422b31401C936172C897802CF0373B35B7698; + + DeployProxyBatchDelegation deployer = new DeployProxyBatchDelegation(); + + ProxyBatchDelegation internal proxyBatchDelegation; + MainnetDelegation internal mainnetDelegation; + + function setUp() external onlyForked { + mainnetDelegation = MainnetDelegation(baseRegistryAddress); + } + + function test_setBatchDelegationCross() external onlyForked { + address getMessenger = mainnetDelegation.getMessenger(); + address getProxyDelegation = mainnetDelegation.getProxyDelegation(); + + MockMessenger mockMessenger = new MockMessenger(); + vm.etch(getMessenger, address(mockMessenger).code); + MockMessenger(getMessenger).setXDomainMessageSender(getProxyDelegation); + + vm.prank(address(getMessenger)); + + (bool success, ) = baseRegistryAddress.call{gas: 400_000}( + // solhint-disable-next-line max-line-length + hex"f59832ed000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000406d4b68d5b0797c5d26437938958f2bb2e9c84e000000000000000000000000a28c8d49957f757dc9eb3df461f0c416f97a2b000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000406d4b68d5b0797c5d26437938958f2bb2e9c84e00000000000000000000000053319181e003e7f86fb79f794649a2ab680db244000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ac7230489e80000" + ); + + // Check the result + require(success, "Call failed"); + } +} diff --git a/contracts/test/mocks/MockMessenger.sol b/contracts/test/mocks/MockMessenger.sol index d007d35625..bcf9cf738c 100644 --- a/contracts/test/mocks/MockMessenger.sol +++ b/contracts/test/mocks/MockMessenger.sol @@ -8,7 +8,7 @@ import {ICrossDomainMessenger} from "./../../src/tokens/river/mainnet/delegation // contracts -contract MockMessenger is ICrossDomainMessenger { +contract MockMessenger { address internal sender; function xDomainMessageSender() external view returns (address) { diff --git a/contracts/test/spaces/BaseSetup.sol b/contracts/test/spaces/BaseSetup.sol index b95c7fcf5e..4445bba318 100644 --- a/contracts/test/spaces/BaseSetup.sol +++ b/contracts/test/spaces/BaseSetup.sol @@ -27,7 +27,7 @@ import {ISpaceDelegation} from "contracts/src/base/registry/facets/delegation/IS // deployments import {DeploySpaceFactory} from "contracts/scripts/deployments/DeploySpaceFactory.s.sol"; import {DeployRiverBase} from "contracts/scripts/deployments/DeployRiverBase.s.sol"; -import {DeployProxyDelegation} from "contracts/scripts/deployments/DeployProxyDelegation.s.sol"; +import {DeployProxyBatchDelegation} from "contracts/scripts/deployments/DeployProxyBatchDelegation.s.sol"; import {DeployBaseRegistry} from "contracts/scripts/deployments/DeployBaseRegistry.s.sol"; /* @@ -38,8 +38,8 @@ contract BaseSetup is TestUtils, SpaceHelper { DeployBaseRegistry internal deployBaseRegistry = new DeployBaseRegistry(); DeploySpaceFactory internal deploySpaceFactory = new DeploySpaceFactory(); DeployRiverBase internal deployRiverTokenBase = new DeployRiverBase(); - DeployProxyDelegation internal deployProxyDelegation = - new DeployProxyDelegation(); + DeployProxyBatchDelegation internal deployProxyBatchDelegation = + new DeployProxyBatchDelegation(); address internal deployer; address internal founder; @@ -81,14 +81,14 @@ contract BaseSetup is TestUtils, SpaceHelper { // Mainnet messenger = MockMessenger(deployBaseRegistry.messenger()); - deployProxyDelegation.setDependencies({ + deployProxyBatchDelegation.setDependencies({ mainnetDelegation_: baseRegistry, messenger_: address(messenger) }); - mainnetProxyDelegation = deployProxyDelegation.deploy(); - mainnetRiverToken = deployProxyDelegation.riverToken(); - vault = deployProxyDelegation.vault(); - claimers = deployProxyDelegation.claimers(); + mainnetProxyDelegation = deployProxyBatchDelegation.deploy(); + mainnetRiverToken = deployProxyBatchDelegation.riverToken(); + vault = deployProxyBatchDelegation.vault(); + claimers = deployProxyBatchDelegation.claimers(); // Space Factory Diamond spaceFactory = deploySpaceFactory.deploy(); diff --git a/contracts/test/spaces/membership/unit/MembershipJoinSpace.t.sol b/contracts/test/spaces/membership/unit/MembershipJoinSpace.t.sol index 0681c0493a..aac57eaca2 100644 --- a/contracts/test/spaces/membership/unit/MembershipJoinSpace.t.sol +++ b/contracts/test/spaces/membership/unit/MembershipJoinSpace.t.sol @@ -7,13 +7,10 @@ import {MembershipBaseSetup} from "../MembershipBaseSetup.sol"; //interfaces import {IEntitlementGated} from "contracts/src/spaces/facets/gated/IEntitlementGated.sol"; import {IEntitlementGatedBase} from "contracts/src/spaces/facets/gated/IEntitlementGated.sol"; -import {IEntitlementChecker, IEntitlementCheckerBase} from "contracts/src/base/registry/facets/checker/IEntitlementChecker.sol"; -import {IRuleEntitlement} from "contracts/src/spaces/entitlements/rule/IRuleEntitlement.sol"; -import {RuleEntitlementUtil} from "contracts/test/crosschain/RuleEntitlementUtil.sol"; +import {IEntitlementCheckerBase} from "contracts/src/base/registry/facets/checker/IEntitlementChecker.sol"; //libraries import {Vm} from "forge-std/Test.sol"; -import {console2} from "forge-std/console2.sol"; //contracts @@ -206,7 +203,7 @@ contract MembershipJoinSpace is bool resultPosted = false; bool tokenEmitted = false; Vm.Log[] memory resultLogs = vm.getRecordedLogs(); // Retrieve the recorded logs - for (uint l = 0; l < resultLogs.length; l++) { + for (uint256 l; l < resultLogs.length; l++) { if (resultLogs[l].topics[0] == RESULT_POSTED) { resultPosted = true; } else if (resultLogs[l].topics[0] == TOKEN_EMITTED) { @@ -356,7 +353,7 @@ contract MembershipJoinSpace is if (k == 2) { Vm.Log[] memory resultLogs = vm.getRecordedLogs(); // Retrieve the recorded logs - for (uint l = 0; l < resultLogs.length; l++) { + for (uint l; l < resultLogs.length; l++) { if (resultLogs[l].topics[0] == RESULT_POSTED) { checkRequestedMatched = true; } diff --git a/contracts/test/spaces/owner/SpaceOwner.t.sol b/contracts/test/spaces/owner/SpaceOwner.t.sol index 44017a16ec..bcaa529745 100644 --- a/contracts/test/spaces/owner/SpaceOwner.t.sol +++ b/contracts/test/spaces/owner/SpaceOwner.t.sol @@ -17,7 +17,7 @@ contract SpaceOwnerTest is ISpaceOwnerBase, IOwnableBase, BaseSetup { string internal name = "Awesome Space"; string internal uri = "ipfs://space-name"; - SpaceOwner spaceOwnerToken; + SpaceOwner internal spaceOwnerToken; function setUp() public override { super.setUp(); diff --git a/contracts/test/spaces/platform/requirements/PlatformRequirements.t.sol b/contracts/test/spaces/platform/requirements/PlatformRequirements.t.sol index f0be621645..981318b34c 100644 --- a/contracts/test/spaces/platform/requirements/PlatformRequirements.t.sol +++ b/contracts/test/spaces/platform/requirements/PlatformRequirements.t.sol @@ -18,7 +18,7 @@ contract PlatformRequirementsTest is IPlatformRequirementsBase, IOwnableBase { - PlatformRequirementsFacet platformReqs; + PlatformRequirementsFacet internal platformReqs; function setUp() public override { super.setUp(); diff --git a/contracts/test/spaces/roles/Roles.t.sol b/contracts/test/spaces/roles/Roles.t.sol index 0bc88d0640..dcf5b9da8b 100644 --- a/contracts/test/spaces/roles/Roles.t.sol +++ b/contracts/test/spaces/roles/Roles.t.sol @@ -27,9 +27,11 @@ import {Validator__InvalidStringLength, Validator__InvalidByteLength} from "cont import {MockUserEntitlement} from "contracts/test/mocks/MockUserEntitlement.sol"; contract RolesTest is BaseSetup, IRolesBase, IEntitlementBase { - function getRandomAddresses(uint N) internal view returns (address[] memory) { + function getRandomAddresses( + uint256 N + ) internal view returns (address[] memory) { address[] memory data = new address[](N); - for (uint i = 0; i < N; i++) { + for (uint256 i = 0; i < N; i++) { data[i] = _randomAddress(); } return data; diff --git a/contracts/test/tokens/delegation/MainnetDelegation.t.sol b/contracts/test/tokens/delegation/MainnetDelegation.t.sol deleted file mode 100644 index f1c2faa4c1..0000000000 --- a/contracts/test/tokens/delegation/MainnetDelegation.t.sol +++ /dev/null @@ -1,317 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -// utils -import {BaseSetup} from "contracts/test/spaces/BaseSetup.sol"; - -//interfaces -import {IMainnetDelegationBase} from "contracts/src/tokens/river/base/delegation/IMainnetDelegation.sol"; -import {IOwnableBase} from "contracts/src/diamond/facets/ownable/IERC173.sol"; - -//libraries - -//contracts -import {River} from "contracts/src/tokens/river/mainnet/River.sol"; -import {MainnetDelegation} from "contracts/src/tokens/river/base/delegation/MainnetDelegation.sol"; -import {ProxyDelegation} from "contracts/src/tokens/river/mainnet/delegation/ProxyDelegation.sol"; -import {ICrossDomainMessenger} from "contracts/src/tokens/river/mainnet/delegation/ICrossDomainMessenger.sol"; -import {AuthorizedClaimers} from "contracts/src/tokens/river/mainnet/claimer/AuthorizedClaimers.sol"; - -/** - * when setDelegation is called - * given caller is not owner - * it should revert with Ownable__NotOwner - * given caller is owner - * when values are valid - * it should set delegation and emit DelegationSet - * when delegator is address(0) - * it should revert with InvalidDelegator - * when operator is address(0) - * it should revert with InvalidOperator - * when quantity is 0 - * it should revert with InvalidQuantity - * when delegation is already set - * it should revert with DelegationAlreadySet - * when removeDelegation is called - * given caller is not owner - * it should revert with Ownable__NotOwner - * given caller is owner - * given delegation is already set - * it should remove delegation and emit DelegationRemoved - * given delegation is not set - * it should revert with DelegationNotSet - * when getDelegationByDelegator is called - * it should return delegation - * when getDelegationsByOperator is called - * it should return delegations - * when getDelegatedStakeByOperator is called - * it should return total delegated stake - */ - -contract MainnetDelegationTest is - BaseSetup, - IMainnetDelegationBase, - IOwnableBase -{ - bytes32 private constant _DELEGATION_TYPEHASH = - keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); - - MainnetDelegation internal delegation; - ProxyDelegation internal proxyDelegation; - AuthorizedClaimers internal authorizedClaimers; - River internal rvr; - ICrossDomainMessenger internal crossDomainMessenger; - - uint256 delegatorKey; - address delegator; - address delegatee; - - function setUp() public override { - super.setUp(); - - delegatorKey = _randomUint256(); - delegator = vm.addr(delegatorKey); - delegatee = _randomAddress(); - - rvr = River(mainnetRiverToken); - proxyDelegation = ProxyDelegation(mainnetProxyDelegation); - crossDomainMessenger = ICrossDomainMessenger(messenger); - - delegation = MainnetDelegation(baseRegistry); - authorizedClaimers = AuthorizedClaimers(claimers); - } - - modifier givenCallerHasTokens() { - vm.prank(vault); - rvr.transfer(delegator, 1 ether); - _; - } - - modifier givenCallerHasDelegatedTokens() { - uint256 nonce = rvr.nonces(delegator); - uint256 expiry = block.timestamp + 1000; - (uint8 v, bytes32 r, bytes32 s) = _signDelegation( - delegatorKey, - delegatee, - nonce, - expiry - ); - proxyDelegation.delegateBySig(delegatee, nonce, expiry, v, r, s); - _; - } - - function test_delegateBySig() - external - givenCallerHasTokens - givenCallerHasDelegatedTokens - { - assertEq(rvr.delegates(delegator), delegatee); - assertEq(delegation.getDelegatedStakeByOperator(delegatee), 1 ether); - - Delegation memory delegationResult = delegation.getDelegationByDelegator( - delegator - ); - - assertEq(delegatee, delegationResult.operator); - } - - // ============================================================= - // MODIFIERS - // ============================================================= - - // modifier givenCallerIsOwner() { - // vm.prank(deployer); - // _; - // } - - // modifier givenDelegationIsAlreadySet( - // address delegator, - // address operator, - // uint256 quantity - // ) { - // vm.assume(delegator != address(0)); - // vm.assume(operator != address(0)); - // vm.assume(quantity != 0); - // delegation.setDelegation(delegator, operator, quantity); - // _; - // } - - // function test_revertWhen_setDelegationNotAsOwner( - // address notOwner, - // address delegator, - // address operator, - // uint256 quantity - // ) public { - // vm.assume(deployer != notOwner); - // vm.prank(notOwner); - // vm.expectRevert( - // abi.encodeWithSelector(Ownable__NotOwner.selector, notOwner) - // ); - // delegation.setDelegation(delegator, operator, quantity); - // } - - // function test_setDelegationAsOwner( - // address delegator, - // address operator, - // uint256 quantity - // ) public givenCallerIsOwner { - // vm.assume(delegator != address(0)); - // vm.assume(operator != address(0)); - // vm.assume(quantity != 0); - - // vm.expectEmit(); - // emit DelegationSet(delegator, operator, quantity); - // delegation.setDelegation(delegator, operator, quantity); - // } - - // function test_revertWhen_setDelegationWithInvalidDelegator( - // address operator, - // uint256 quantity - // ) public givenCallerIsOwner { - // address delegator = address(0); - // vm.expectRevert( - // abi.encodeWithSelector(InvalidDelegator.selector, delegator) - // ); - // delegation.setDelegation(delegator, operator, quantity); - // } - - // function test_revertWhen_setDelegationWithInvalidOperator( - // address delegator, - // uint256 quantity - // ) public givenCallerIsOwner { - // vm.assume(delegator != address(0)); - // vm.assume(quantity != 0); - - // address operator = address(0); - // vm.expectRevert(abi.encodeWithSelector(InvalidOperator.selector, operator)); - // delegation.setDelegation(delegator, operator, quantity); - // } - - // function test_revertWhen_setDelegationWithInvalidQuantity( - // address delegator, - // address operator - // ) public givenCallerIsOwner { - // vm.assume(delegator != address(0)); - // vm.assume(operator != address(0)); - - // uint256 quantity = 0; - // vm.expectRevert(abi.encodeWithSelector(InvalidQuantity.selector, quantity)); - // delegation.setDelegation(delegator, operator, quantity); - // } - - // function test_revertWhen_setDelegationWithDelegationAlreadySet( - // address delegator, - // address operator, - // uint256 quantity - // ) - // public - // givenCallerIsOwner - // givenDelegationIsAlreadySet(delegator, operator, quantity) - // { - // vm.prank(deployer); - // vm.expectRevert( - // abi.encodeWithSelector(DelegationAlreadySet.selector, delegator, operator) - // ); - // delegation.setDelegation(delegator, operator, quantity); - // } - - // function test_revertWhen_removeDelegationNotAsOwner(address notOwner) public { - // vm.assume(deployer != notOwner); - // vm.prank(notOwner); - // vm.expectRevert( - // abi.encodeWithSelector(Ownable__NotOwner.selector, notOwner) - // ); - // delegation.setDelegation(notOwner, address(0), 0); - // } - - // function test_removeDelegationAsOwner( - // address delegator, - // address operator, - // uint256 quantity - // ) - // public - // givenCallerIsOwner - // givenDelegationIsAlreadySet(delegator, operator, quantity) - // { - // vm.prank(deployer); - // vm.expectEmit(); - // emit DelegationRemoved(delegator); - // delegation.setDelegation(delegator, address(0), 0); - // } - - // function test_revertWhen_removeDelegationWithDelegationNotSet( - // address delegator - // ) public givenCallerIsOwner { - // vm.expectRevert(DelegationNotSet.selector); - // delegation.setDelegation(delegator, address(0), 0); - // } - - // function test_getDelegationByDelegator( - // address delegator, - // address operator, - // uint256 quantity - // ) - // public - // givenCallerIsOwner - // givenDelegationIsAlreadySet(delegator, operator, quantity) - // { - // Delegation memory delegationResult = delegation.getDelegationByDelegator( - // delegator - // ); - - // assertEq(operator, delegationResult.operator); - // assertEq(quantity, delegationResult.quantity); - // } - - // function test_getMainnetDelegationsByOperator( - // address delegator, - // address operator, - // uint256 quantity - // ) - // public - // givenCallerIsOwner - // givenDelegationIsAlreadySet(delegator, operator, quantity) - // { - // Delegation[] memory delegations = delegation.getDelegationsByOperator( - // operator - // ); - // assertEq(1, delegations.length); - // assertEq(operator, delegations[0].operator); - // assertEq(quantity, delegations[0].quantity); - // } - - // function test_getDelegatedStakeByOperator( - // address delegator, - // address operator, - // uint256 quantity - // ) - // public - // givenCallerIsOwner - // givenDelegationIsAlreadySet(delegator, operator, quantity) - // { - // uint256 delegatedStake = delegation.getDelegatedStakeByOperator(operator); - // assertEq(quantity, delegatedStake); - // } - - // ============================================================= - // Internal - // ============================================================= - function _signDelegation( - uint256 _privateKey, - address _delegatee, - uint256 _nonce, - uint256 _expiry - ) internal view returns (uint8 v, bytes32 r, bytes32 s) { - bytes32 domainSeparator = rvr.DOMAIN_SEPARATOR(); - - bytes32 structHash = keccak256( - abi.encode(_DELEGATION_TYPEHASH, _delegatee, _nonce, _expiry) - ); - - bytes32 typeDataHash = keccak256( - abi.encodePacked("\x19\x01", domainSeparator, structHash) - ); - - (v, r, s) = vm.sign(_privateKey, typeDataHash); - } -} diff --git a/contracts/test/tokens/delegation/ProxyBatchDelegation.t.sol b/contracts/test/tokens/delegation/ProxyBatchDelegation.t.sol new file mode 100644 index 0000000000..aa3f292eb5 --- /dev/null +++ b/contracts/test/tokens/delegation/ProxyBatchDelegation.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.23; + +// interfaces +import {IMainnetDelegationBase} from "contracts/src/tokens/river/base/delegation/IMainnetDelegation.sol"; + +// libraries + +// contracts +import {BaseSetup} from "contracts/test/spaces/BaseSetup.sol"; + +// deps +import {River} from "contracts/src/tokens/river/mainnet/River.sol"; +import {MainnetDelegation} from "contracts/src/tokens/river/base/delegation/MainnetDelegation.sol"; +import {ProxyBatchDelegation} from "contracts/src/tokens/river/mainnet/delegation/ProxyBatchDelegation.sol"; +import {ICrossDomainMessenger} from "contracts/src/tokens/river/mainnet/delegation/ICrossDomainMessenger.sol"; +import {AuthorizedClaimers} from "contracts/src/tokens/river/mainnet/claimer/AuthorizedClaimers.sol"; + +contract ProxyBatchDelegationTest is BaseSetup, IMainnetDelegationBase { + MainnetDelegation internal delegation; + ProxyBatchDelegation internal proxyDelegation; + AuthorizedClaimers internal authorizedClaimers; + River internal rvr; + ICrossDomainMessenger internal crossDomainMessenger; + + address[] internal _users; + address[] internal _operators; + address[] internal _claimers; + uint256 internal tokens; + + function setUp() public override { + super.setUp(); + + _users = _createAccounts(10); + _operators = _createAccounts(5); + _claimers = _createAccounts(5); + tokens = 10 ether; + + rvr = River(mainnetRiverToken); + proxyDelegation = ProxyBatchDelegation(mainnetProxyDelegation); + crossDomainMessenger = ICrossDomainMessenger(address(messenger)); + + delegation = MainnetDelegation(baseRegistry); + authorizedClaimers = AuthorizedClaimers(claimers); + } + + modifier givenUsersHaveTokens() { + for (uint256 i = 0; i < _users.length; i++) { + vm.prank(vault); + rvr.transfer(_users[i], tokens); + } + _; + } + + modifier givenUsersHaveAuthorizedClaimers() { + for (uint256 i = 0; i < _users.length; i++) { + vm.prank(_users[i]); + authorizedClaimers.authorizeClaimer(_getRandomValue(_claimers)); + } + _; + } + + modifier givenUsersHaveDelegatedTokens() { + for (uint256 i = 0; i < _users.length; i++) { + vm.prank(_users[i]); + rvr.delegate(_getRandomValue(_operators)); + } + _; + } + + function test_sendDelegations() + external + givenUsersHaveTokens + givenUsersHaveAuthorizedClaimers + givenUsersHaveDelegatedTokens + { + vm.prank(_randomAddress()); + proxyDelegation.sendDelegators(); + + address randomUser = _getRandomValue(_users); + + Delegation memory delegator = delegation.getDelegationByDelegator( + randomUser + ); + + assertEq(rvr.delegates(randomUser), delegator.operator); + assertEq( + authorizedClaimers.getAuthorizedClaimer(randomUser), + delegation.getAuthorizedClaimer(randomUser) + ); + } + + function _getRandomValue( + address[] memory addresses + ) internal view returns (address) { + require(addresses.length > 0, "No addresses available"); + + // Generate a pseudo-random index based on block properties + uint256 randomIndex = uint256( + keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender)) + ) % addresses.length; + + return addresses[randomIndex]; + } +} diff --git a/scripts/deploy-contracts.sh b/scripts/deploy-contracts.sh index d568d774fc..3ad4b2c013 100755 --- a/scripts/deploy-contracts.sh +++ b/scripts/deploy-contracts.sh @@ -47,7 +47,7 @@ cast rpc evm_setAutomine true --rpc-url $RIVER_ANVIL_RPC_URL # Space Architect make clear-anvil-deployments make deploy-base-anvil type=contract contract=DeployBaseRegistry -make deploy-base-anvil type=contract contract=DeployProxyDelegation +make deploy-base-anvil type=contract contract=DeployProxyBatchDelegation make deploy-base-anvil type=contract contract=DeployRiverBase make deploy-base-anvil type=contract contract=DeploySpaceFactory make interact-base-anvil type=contract contract=InteractPostDeploy