From 1578c12fa82ba816ecd942495990f763b95e5be7 Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Sun, 18 Feb 2024 21:25:37 +0100 Subject: [PATCH 01/18] refactor(contracts): align forwarders to new ptoken minting logic --- contracts/forwarder/ForwarderHost.sol | 110 ++ .../{Forwarder.sol => ForwarderNative.sol} | 28 +- contracts/interfaces/external/IPReceiver.sol | 18 + contracts/test/MockPToken.sol | 92 +- test/Forwarders.test.js | 1068 +++++++++-------- test/fork/dao.test.js | 38 +- 6 files changed, 782 insertions(+), 572 deletions(-) create mode 100644 contracts/forwarder/ForwarderHost.sol rename contracts/forwarder/{Forwarder.sol => ForwarderNative.sol} (84%) create mode 100644 contracts/interfaces/external/IPReceiver.sol diff --git a/contracts/forwarder/ForwarderHost.sol b/contracts/forwarder/ForwarderHost.sol new file mode 100644 index 0000000..eff634b --- /dev/null +++ b/contracts/forwarder/ForwarderHost.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {Context} from "@openzeppelin/contracts/utils/Context.sol"; +import {IERC777Recipient} from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol"; +import {IERC1820Registry} from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IForwarder} from "../interfaces/IForwarder.sol"; +import {IPReceiver} from "../interfaces/external/IPReceiver.sol"; +import {IPToken} from "../interfaces/external/IPToken.sol"; +import {Helpers} from "../libraries/Helpers.sol"; +import {BytesLib} from "../libraries/BytesLib.sol"; + +error CallFailed(address target, bytes data); +error InvalidCallParams(address[] targets, bytes[] data, address caller); +error InvalidOriginAddress(address originAddress); +error InvalidCaller(address caller); + +contract ForwarderHost is IForwarder, IERC777Recipient, Context, Ownable, IPReceiver { + using SafeERC20 for IERC20; + + address public immutable token; + mapping(address => bool) private _whitelistedOriginAddresses; + + constructor(address _token) { + IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24).setInterfaceImplementer( + address(this), + keccak256("ERC777TokensRecipient"), + address(this) + ); + token = _token; + } + + /// @inheritdoc IForwarder + function call(uint256 amount, address to, bytes calldata data, bytes4 chainId) external { + address msgSender = _msgSender(); + if (amount > 0) { + IERC20(token).safeTransferFrom(msgSender, address(this), amount); + } + + bytes memory effectiveUserData = abi.encode(data, msgSender); + uint256 effectiveAmount = amount == 0 ? 1 : amount; + + IPToken(token).redeem(effectiveAmount, effectiveUserData, Helpers.addressToAsciiString(to), chainId); + } + + function receiveUserData(bytes calldata _metadata) external override { + if (_msgSender() == token) + _processMetadata(_metadata); + } + + function tokensReceived( + address /*_operator*/, + address _from, + address /*_to,*/, + uint256 /*_amount*/, + bytes calldata _metadata, + bytes calldata /*_operatorData*/ + ) external override { + if (_msgSender() == token && _from == address(0)) + _processMetadata(_metadata); + } + + function whitelistOriginAddress(address originAddress) external onlyOwner { + _whitelistedOriginAddresses[originAddress] = true; + } + + function _processMetadata(bytes memory _metadata) private { + (, bytes memory userData, , address originAddress, , , , ) = abi.decode( + _metadata, + (bytes1, bytes, bytes4, address, bytes4, address, bytes, bytes) + ); + + (bytes memory callsAndTargets, address caller) = abi.decode(userData, (bytes, address)); + if (!_whitelistedOriginAddresses[originAddress]) { + revert InvalidOriginAddress(originAddress); + } + + (address[] memory targets, bytes[] memory data) = abi.decode(callsAndTargets, (address[], bytes[])); + + if (targets.length != data.length) { + revert InvalidCallParams(targets, data, caller); + } + + for (uint256 i = 0; i < targets.length; ) { + // NOTE: avoid to check the caller if function is approve + if (bytes4(data[i]) != 0x095ea7b3) { + bytes memory addrSlot = BytesLib.slice(data[i], 4, 36); + address expectedCaller = address(BytesLib.toAddress(addrSlot, 32 - 20)); + + // NOTE: needed to for example avoid someone to vote for someone else + if (expectedCaller != caller) { + revert InvalidCaller(expectedCaller); + } + } + + (bool success, ) = targets[i].call(data[i]); + if (!success) { + revert CallFailed(targets[i], data[i]); + } + + unchecked { + ++i; + } + } + } +} diff --git a/contracts/forwarder/Forwarder.sol b/contracts/forwarder/ForwarderNative.sol similarity index 84% rename from contracts/forwarder/Forwarder.sol rename to contracts/forwarder/ForwarderNative.sol index 1099618..99da229 100644 --- a/contracts/forwarder/Forwarder.sol +++ b/contracts/forwarder/ForwarderNative.sol @@ -19,22 +19,20 @@ error InvalidCallParams(address[] targets, bytes[] data, address caller); error InvalidOriginAddress(address originAddress); error InvalidCaller(address caller); -contract Forwarder is IForwarder, IERC777Recipient, Context, Ownable { +contract ForwarderNative is IForwarder, IERC777Recipient, Context, Ownable { using SafeERC20 for IERC20; - address public immutable sender; address public immutable token; address public immutable vault; mapping(address => bool) private _whitelistedOriginAddresses; - constructor(address _token, address _sender, address _vault) { + constructor(address _token, address _vault) { IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24).setInterfaceImplementer( address(this), keccak256("ERC777TokensRecipient"), address(this) ); - sender = _sender; token = _token; vault = _vault; // set it to 0 on an host chain } @@ -47,7 +45,7 @@ contract Forwarder is IForwarder, IERC777Recipient, Context, Ownable { bytes calldata _userData, bytes calldata /*_operatorData*/ ) external override { - if (_msgSender() == token && _from == sender) { + if (_msgSender() == token && _from == vault) { (, bytes memory userData, , address originAddress, , , , ) = abi.decode( _userData, (bytes1, bytes, bytes4, address, bytes4, address, bytes, bytes) @@ -102,18 +100,14 @@ contract Forwarder is IForwarder, IERC777Recipient, Context, Ownable { bytes memory effectiveUserData = abi.encode(data, msgSender); uint256 effectiveAmount = amount == 0 ? 1 : amount; - if (vault != address(0)) { - IERC20(token).safeApprove(vault, effectiveAmount); - IErc20Vault(vault).pegIn( - effectiveAmount, - token, - Helpers.addressToAsciiString(to), - effectiveUserData, - chainId - ); - } else { - IPToken(token).redeem(effectiveAmount, effectiveUserData, Helpers.addressToAsciiString(to), chainId); - } + IERC20(token).safeApprove(vault, effectiveAmount); + IErc20Vault(vault).pegIn( + effectiveAmount, + token, + Helpers.addressToAsciiString(to), + effectiveUserData, + chainId + ); } function whitelistOriginAddress(address originAddress) external onlyOwner { diff --git a/contracts/interfaces/external/IPReceiver.sol b/contracts/interfaces/external/IPReceiver.sol new file mode 100644 index 0000000..3999220 --- /dev/null +++ b/contracts/interfaces/external/IPReceiver.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +/** + * @title IPReceiver + * @author pNetwork + * + * @dev Interface for contracts excpecting cross-chain data + */ +interface IPReceiver { + /* + * @dev Function called when userData.length > 0 when minting the pToken + * + * @param userData + */ + function receiveUserData(bytes calldata userData) external; +} diff --git a/contracts/test/MockPToken.sol b/contracts/test/MockPToken.sol index c4291ba..dda8268 100644 --- a/contracts/test/MockPToken.sol +++ b/contracts/test/MockPToken.sol @@ -3,8 +3,10 @@ pragma solidity ^0.8.17; import "@openzeppelin/contracts/token/ERC777/ERC777.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IPReceiver} from "../interfaces/external/IPReceiver.sol"; -contract MockPToken is ERC777 { +contract MockPTokenERC777 is ERC777 { address public pNetwork; bytes4 public ORIGIN_CHAIN_ID; @@ -19,10 +21,9 @@ contract MockPToken is ERC777 { constructor( string memory tokenName, string memory tokenSymbol, - address[] memory defaultOperators, address _pnetwork, bytes4 originChainId - ) ERC777(tokenName, tokenSymbol, defaultOperators) { + ) ERC777(tokenName, tokenSymbol, new address[](0)) { pNetwork = _pnetwork; ORIGIN_CHAIN_ID = originChainId; } @@ -54,17 +55,82 @@ contract MockPToken is ERC777 { return true; } - function operatorRedeem( - address account, + function redeem( + uint256 amount, + bytes memory data, + string memory underlyingAssetRecipient, + bytes4 destinationChainId + ) public { + _burn(_msgSender(), amount, data, ""); + emit Redeem(msg.sender, amount, underlyingAssetRecipient, ORIGIN_CHAIN_ID, destinationChainId); + } + + function owner() internal view returns (address) { + return pNetwork; + } +} + +contract MockPTokenERC20 is ERC20 { + address public pNetwork; + bytes4 public ORIGIN_CHAIN_ID; + + event ReceiveUserDataFailed(); + + event Redeem( + address indexed redeemer, + uint256 value, + string underlyingAssetRecipient, + bytes userData, + bytes4 originChainId, + bytes4 destinationChainId + ); + + constructor( + string memory tokenName, + string memory tokenSymbol, + address _pnetwork, + bytes4 originChainId + ) ERC20(tokenName, tokenSymbol) { + pNetwork = _pnetwork; + ORIGIN_CHAIN_ID = originChainId; + } + + function changePNetwork(address newPNetwork) external { + require(_msgSender() == pNetwork, "Only the pNetwork can change the `pNetwork` account!"); + require(newPNetwork != address(0), "pNetwork cannot be the zero address!"); + pNetwork = newPNetwork; + } + + function mint( + address recipient, + uint256 value, + bytes calldata userData, + bytes calldata + ) external returns (bool) { + require(_msgSender() == pNetwork, "Only the pNetwork can mint tokens!"); + require(recipient != address(0), "pToken: Cannot mint to the zero address!"); + _mint(recipient, value); + if (userData.length > 0) { + // pNetwork aims to deliver cross chain messages successfully regardless of what the user may do with them. + // We do not want this mint transaction reverting if their receiveUserData function reverts, + // and thus we swallow any such errors, emitting a `ReceiveUserDataFailed` event instead. + // The low-level call is used because in the solidity version this contract was written in, + // a try/catch block fails to catch the revert caused if the receiver is not in fact a contract. + // This way, a user also has the option include userData even when minting to an externally owned account. + bytes memory data = abi.encodeWithSelector(IPReceiver.receiveUserData.selector, userData); + (bool success, ) = recipient.call(data); + if (!success) emit ReceiveUserDataFailed(); + } + return true; + } + + function redeem( uint256 amount, - bytes calldata data, - bytes calldata operatorData, string calldata underlyingAssetRecipient, bytes4 destinationChainId - ) external { - require(isOperatorFor(_msgSender(), account), "ERC777: caller is not an operator for holder"); - _burn(account, amount, data, operatorData); - emit Redeem(account, amount, underlyingAssetRecipient, ORIGIN_CHAIN_ID, destinationChainId); + ) external returns (bool) { + redeem(amount, "", underlyingAssetRecipient, destinationChainId); + return true; } function redeem( @@ -73,8 +139,8 @@ contract MockPToken is ERC777 { string memory underlyingAssetRecipient, bytes4 destinationChainId ) public { - _burn(_msgSender(), amount, data, ""); - emit Redeem(msg.sender, amount, underlyingAssetRecipient, ORIGIN_CHAIN_ID, destinationChainId); + _burn(_msgSender(), amount); + emit Redeem(_msgSender(), amount, underlyingAssetRecipient, data, ORIGIN_CHAIN_ID, destinationChainId); } function owner() internal view returns (address) { diff --git a/test/Forwarders.test.js b/test/Forwarders.test.js index 7347bcd..11ead97 100644 --- a/test/Forwarders.test.js +++ b/test/Forwarders.test.js @@ -16,8 +16,7 @@ const { PNT_MAX_TOTAL_SUPPLY, REGISTRATION_SENTINEL_BORROWING, REGISTRATION_SENTINEL_STAKING, - TOKEN_MANAGER_ADDRESS, - ZERO_ADDRESS + TOKEN_MANAGER_ADDRESS } = require('./constants') const { BORROW_ROLE, @@ -53,581 +52,604 @@ let acl, forwarderRecipientUpgradeableTestData, fakeDandelionVoting -describe('Forwarders', () => { - beforeEach(async () => { - const Forwarder = await ethers.getContractFactory('Forwarder') - const StakingManager = await ethers.getContractFactory('StakingManager') - const StakingManagerPermissioned = await ethers.getContractFactory('StakingManagerPermissioned') - const LendingManager = await ethers.getContractFactory('LendingManager') - const RegistrationManager = await ethers.getContractFactory('RegistrationManager') - const EpochsManager = await ethers.getContractFactory('EpochsManager') - const MockPToken = await ethers.getContractFactory('MockPToken') - const MockPTokensVault = await ethers.getContractFactory('MockPTokensVault') - const TestToken = await ethers.getContractFactory('TestToken') - const ACL = await ethers.getContractFactory('ACL') - const DandelionVoting = await ethers.getContractFactory('DandelionVoting') - - const signers = await ethers.getSigners() - owner = signers[0] - sentinel1 = signers[1] - fakeForwarder = signers[2] - fakeDandelionVoting = signers[3] - pnetwork = await ethers.getImpersonatedSigner(PNETWORK_ADDRESS) - pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) - pntHolder2 = await ethers.getImpersonatedSigner(PNT_HOLDER_2_ADDRESS) - root = await ethers.getImpersonatedSigner(DAO_ROOT_ADDRESS) - - acl = ACL.attach(ACL_ADDRESS) - pnt = await TestToken.deploy('PNT', 'PNT') - vault = await MockPTokensVault.deploy(PNETWORK_NETWORK_IDS.ethereumMainnet) - pToken = await MockPToken.connect(owner).deploy( - 'Host Token (pToken)', - 'HTKN', - [], - pnetwork.address, - PNETWORK_NETWORK_IDS.gnosisMainnet - ) - await pnt.connect(owner).transfer(pntHolder1.address, ethers.parseEther('400000')) - await pnt.connect(owner).transfer(pntHolder2.address, ethers.parseEther('400000')) - - forwarderNative = await Forwarder.deploy(await pnt.getAddress(), await vault.getAddress(), await vault.getAddress()) - forwarderHost = await Forwarder.deploy(await pToken.getAddress(), ZERO_ADDRESS, ZERO_ADDRESS) - await forwarderNative.whitelistOriginAddress(await forwarderHost.getAddress()) - await forwarderHost.whitelistOriginAddress(await forwarderNative.getAddress()) - - stakingManager = await upgrades.deployProxy( - StakingManager, - [await pToken.getAddress(), TOKEN_MANAGER_ADDRESS, await forwarderHost.getAddress(), PNT_MAX_TOTAL_SUPPLY], - { - initializer: 'initialize', - kind: 'uups' - } - ) +const MOCK_PTOKEN_ERC777 = 'MockPTokenERC777' +const MOCK_PTOKEN_ERC20 = 'MockPTokenERC20' +const PTOKEN_CONTRACTS = [MOCK_PTOKEN_ERC777, MOCK_PTOKEN_ERC20] + +PTOKEN_CONTRACTS.map((_ptokenContract) => + describe(`Forwarders with host pToken as ${_ptokenContract.slice(4)}`, () => { + beforeEach(async () => { + const ForwarderNative = await ethers.getContractFactory('ForwarderNative') + const ForwarderHost = await ethers.getContractFactory('ForwarderHost') + const StakingManager = await ethers.getContractFactory('StakingManager') + const StakingManagerPermissioned = await ethers.getContractFactory('StakingManagerPermissioned') + const LendingManager = await ethers.getContractFactory('LendingManager') + const RegistrationManager = await ethers.getContractFactory('RegistrationManager') + const EpochsManager = await ethers.getContractFactory('EpochsManager') + const MockPToken = await ethers.getContractFactory(_ptokenContract) + const MockPTokensVault = await ethers.getContractFactory('MockPTokensVault') + const TestToken = await ethers.getContractFactory('TestToken') + const ACL = await ethers.getContractFactory('ACL') + const DandelionVoting = await ethers.getContractFactory('DandelionVoting') + + const signers = await ethers.getSigners() + owner = signers[0] + sentinel1 = signers[1] + fakeForwarder = signers[2] + fakeDandelionVoting = signers[3] + pnetwork = await ethers.getImpersonatedSigner(PNETWORK_ADDRESS) + pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) + pntHolder2 = await ethers.getImpersonatedSigner(PNT_HOLDER_2_ADDRESS) + root = await ethers.getImpersonatedSigner(DAO_ROOT_ADDRESS) + + acl = ACL.attach(ACL_ADDRESS) + pnt = await TestToken.deploy('PNT', 'PNT') + vault = await MockPTokensVault.deploy(PNETWORK_NETWORK_IDS.ethereumMainnet) + pToken = await MockPToken.connect(owner).deploy( + 'Host Token (pToken)', + 'HTKN', + pnetwork.address, + PNETWORK_NETWORK_IDS.gnosisMainnet + ) + await pnt.connect(owner).transfer(pntHolder1.address, ethers.parseEther('400000')) + await pnt.connect(owner).transfer(pntHolder2.address, ethers.parseEther('400000')) + + forwarderNative = await ForwarderNative.deploy(await pnt.getAddress(), await vault.getAddress()) + forwarderHost = await ForwarderHost.deploy(await pToken.getAddress()) + await forwarderNative.whitelistOriginAddress(await forwarderHost.getAddress()) + await forwarderHost.whitelistOriginAddress(await forwarderNative.getAddress()) + + stakingManager = await upgrades.deployProxy( + StakingManager, + [await pToken.getAddress(), TOKEN_MANAGER_ADDRESS, await forwarderHost.getAddress(), PNT_MAX_TOTAL_SUPPLY], + { + initializer: 'initialize', + kind: 'uups' + } + ) - stakingManagerLM = await upgrades.deployProxy( - StakingManagerPermissioned, - [await pToken.getAddress(), TOKEN_MANAGER_ADDRESS, await forwarderHost.getAddress(), PNT_MAX_TOTAL_SUPPLY], - { - initializer: 'initialize', - kind: 'uups' - } - ) + stakingManagerLM = await upgrades.deployProxy( + StakingManagerPermissioned, + [await pToken.getAddress(), TOKEN_MANAGER_ADDRESS, await forwarderHost.getAddress(), PNT_MAX_TOTAL_SUPPLY], + { + initializer: 'initialize', + kind: 'uups' + } + ) + + stakingManagerRM = await upgrades.deployProxy( + StakingManagerPermissioned, + [await pToken.getAddress(), TOKEN_MANAGER_ADDRESS, await forwarderHost.getAddress(), PNT_MAX_TOTAL_SUPPLY], + { + initializer: 'initialize', + kind: 'uups' + } + ) - stakingManagerRM = await upgrades.deployProxy( - StakingManagerPermissioned, - [await pToken.getAddress(), TOKEN_MANAGER_ADDRESS, await forwarderHost.getAddress(), PNT_MAX_TOTAL_SUPPLY], - { + epochsManager = await upgrades.deployProxy(EpochsManager, [EPOCH_DURATION, 0], { initializer: 'initialize', kind: 'uups' - } - ) + }) - epochsManager = await upgrades.deployProxy(EpochsManager, [EPOCH_DURATION, 0], { - initializer: 'initialize', - kind: 'uups' - }) + lendingManager = await upgrades.deployProxy( + LendingManager, + [ + await pToken.getAddress(), + await stakingManagerLM.getAddress(), + await epochsManager.getAddress(), + await forwarderHost.getAddress(), + fakeDandelionVoting.address, + LEND_MAX_EPOCHS + ], + { + initializer: 'initialize', + kind: 'uups' + } + ) - lendingManager = await upgrades.deployProxy( - LendingManager, - [ - await pToken.getAddress(), - await stakingManagerLM.getAddress(), - await epochsManager.getAddress(), - await forwarderHost.getAddress(), - fakeDandelionVoting.address, - LEND_MAX_EPOCHS - ], - { - initializer: 'initialize', - kind: 'uups' - } - ) - - registrationManager = await upgrades.deployProxy( - RegistrationManager, - [ - await pToken.getAddress(), - await stakingManagerRM.getAddress(), - await epochsManager.getAddress(), - await lendingManager.getAddress(), - await forwarderHost.getAddress() - ], - { - initializer: 'initialize', - kind: 'uups' - } - ) - - voting = await DandelionVoting.deploy(await forwarderHost.getAddress()) - await voting.setForwarder(await forwarderHost.getAddress()) - - // set permissions - await acl.connect(root).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) - await acl.connect(root).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) - await acl.connect(root).grantPermission(await stakingManagerRM.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) - await acl.connect(root).grantPermission(await stakingManagerRM.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) - await acl.connect(root).grantPermission(await stakingManagerLM.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) - await acl.connect(root).grantPermission(await stakingManagerLM.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) - await lendingManager.grantRole(BORROW_ROLE, await registrationManager.getAddress()) - await stakingManagerLM.grantRole(STAKE_ROLE, await lendingManager.getAddress()) - await stakingManagerLM.grantRole(INCREASE_DURATION_ROLE, await lendingManager.getAddress()) - await stakingManagerRM.grantRole(STAKE_ROLE, await registrationManager.getAddress()) - await stakingManagerRM.grantRole(INCREASE_DURATION_ROLE, await registrationManager.getAddress()) - await stakingManager.grantRole(UPGRADE_ROLE, owner.address) - await lendingManager.grantRole(UPGRADE_ROLE, owner.address) - await registrationManager.grantRole(UPGRADE_ROLE, owner.address) - - await stakingManager.setForwarder(await forwarderHost.getAddress()) - await lendingManager.setForwarder(await forwarderHost.getAddress()) - await registrationManager.setForwarder(await forwarderHost.getAddress()) - - await owner.sendTransaction({ - to: pntHolder1.address, - value: ethers.parseEther('10') - }) - await owner.sendTransaction({ - to: pntHolder2.address, - value: ethers.parseEther('10') + registrationManager = await upgrades.deployProxy( + RegistrationManager, + [ + await pToken.getAddress(), + await stakingManagerRM.getAddress(), + await epochsManager.getAddress(), + await lendingManager.getAddress(), + await forwarderHost.getAddress() + ], + { + initializer: 'initialize', + kind: 'uups' + } + ) + + voting = await DandelionVoting.deploy(await forwarderHost.getAddress()) + await voting.setForwarder(await forwarderHost.getAddress()) + + // set permissions + await acl.connect(root).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) + await acl.connect(root).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) + await acl.connect(root).grantPermission(await stakingManagerRM.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) + await acl.connect(root).grantPermission(await stakingManagerRM.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) + await acl.connect(root).grantPermission(await stakingManagerLM.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) + await acl.connect(root).grantPermission(await stakingManagerLM.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) + await lendingManager.grantRole(BORROW_ROLE, await registrationManager.getAddress()) + await stakingManagerLM.grantRole(STAKE_ROLE, await lendingManager.getAddress()) + await stakingManagerLM.grantRole(INCREASE_DURATION_ROLE, await lendingManager.getAddress()) + await stakingManagerRM.grantRole(STAKE_ROLE, await registrationManager.getAddress()) + await stakingManagerRM.grantRole(INCREASE_DURATION_ROLE, await registrationManager.getAddress()) + await stakingManager.grantRole(UPGRADE_ROLE, owner.address) + await lendingManager.grantRole(UPGRADE_ROLE, owner.address) + await registrationManager.grantRole(UPGRADE_ROLE, owner.address) + + await stakingManager.setForwarder(await forwarderHost.getAddress()) + await lendingManager.setForwarder(await forwarderHost.getAddress()) + await registrationManager.setForwarder(await forwarderHost.getAddress()) + + await owner.sendTransaction({ + to: pntHolder1.address, + value: ethers.parseEther('10') + }) + await owner.sendTransaction({ + to: pntHolder2.address, + value: ethers.parseEther('10') + }) + await owner.sendTransaction({ + to: pnetwork.address, + value: ethers.parseEther('10') + }) + await pnt.connect(owner).transfer(await forwarderNative.getAddress(), ethers.parseEther('10000')) + + forwarderRecipientUpgradeableTestData = [ + { + artifact: StakingManager, + contract: stakingManager + }, + { + artifact: LendingManager, + contract: lendingManager + }, + { + artifact: RegistrationManager, + contract: registrationManager + } + ] }) - await owner.sendTransaction({ - to: pnetwork.address, - value: ethers.parseEther('10') + + describe('ForwarderRecipientUpgradeable', () => { + it('should not be able to change the forwarder without the corresponding role', async () => { + for (const { contract } of forwarderRecipientUpgradeableTestData) { + const expectedError = `AccessControl: account ${pntHolder1.address.toLowerCase()} is missing role ${SET_FORWARDER_ROLE}` + await expect(contract.connect(pntHolder1).setForwarder(fakeForwarder.address)).to.be.revertedWith( + expectedError + ) + } + }) + + it('should be able to change the forwarder', async () => { + for (const { contract } of forwarderRecipientUpgradeableTestData) { + await contract.grantRole(SET_FORWARDER_ROLE, pntHolder1.address) + await contract.connect(pntHolder1).setForwarder(fakeForwarder.address) + expect(await contract.forwarder()).to.be.eq(fakeForwarder.address) + } + }) + + it('should be able to change the forwarder after a contract upgrade', async () => { + for (const { artifact, contract } of forwarderRecipientUpgradeableTestData) { + await contract.grantRole(SET_FORWARDER_ROLE, pntHolder1.address) + await contract.connect(pntHolder1).setForwarder(fakeForwarder.address) + expect(await contract.forwarder()).to.be.eq(fakeForwarder.address) + await upgrades.upgradeProxy(await contract.getAddress(), artifact, { + kind: 'uups' + }) + await contract.connect(pntHolder1).setForwarder(await forwarderHost.getAddress()) + expect(await contract.forwarder()).to.be.eq(await forwarderHost.getAddress()) + } + }) }) - await pnt.connect(owner).transfer(await forwarderNative.getAddress(), ethers.parseEther('10000')) - - forwarderRecipientUpgradeableTestData = [ - { - artifact: StakingManager, - contract: stakingManager - }, - { - artifact: LendingManager, - contract: lendingManager - }, - { - artifact: RegistrationManager, - contract: registrationManager - } - ] - }) - describe('ForwarderRecipientUpgradeable', () => { - it('should not be able to change the forwarder without the corresponding role', async () => { - for (const { contract } of forwarderRecipientUpgradeableTestData) { - const expectedError = `AccessControl: account ${pntHolder1.address.toLowerCase()} is missing role ${SET_FORWARDER_ROLE}` - await expect(contract.connect(pntHolder1).setForwarder(fakeForwarder.address)).to.be.revertedWith(expectedError) - } + it('should be able to forward a vote', async () => { + const voteId = 1 + const dandelionVotingInterface = new ethers.Interface([ + 'function delegateVote(address voter, uint256 _voteId, bool _supports)' + ]) + const userData = encode( + ['address[]', 'bytes[]'], + [ + [await voting.getAddress()], + [dandelionVotingInterface.encodeFunctionData('delegateVote', [pntHolder1.address, voteId, true])] + ] + ) + + await expect( + forwarderNative + .connect(pntHolder1) + .call(0, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) + ) + .to.emit(vault, 'PegIn') + .withArgs( + await pnt.getAddress(), + await forwarderNative.getAddress(), + 1, + (await forwarderHost.getAddress()).toLowerCase().slice(2), + getUserDataGeneratedByForwarder(userData, pntHolder1.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + PNETWORK_NETWORK_IDS.gnosisMainnet + ) + + // NOTE: at this point let's suppose that a pNetwork node processes the pegin ... + + const metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], + [ + '0x02', + getUserDataGeneratedByForwarder(userData, pntHolder1.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + await forwarderNative.getAddress(), + PNETWORK_NETWORK_IDS.gnosisMainnet, + await forwarderHost.getAddress(), + '0x', + '0x' + ] + ) + + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), 0, metadata, '0x')) + .to.emit(voting, 'CastVote') + .withArgs(voteId, pntHolder1.address, true) }) - it('should be able to change the forwarder', async () => { - for (const { contract } of forwarderRecipientUpgradeableTestData) { - await contract.grantRole(SET_FORWARDER_ROLE, pntHolder1.address) - await contract.connect(pntHolder1).setForwarder(fakeForwarder.address) - expect(await contract.forwarder()).to.be.eq(fakeForwarder.address) - } + it('should not be able to forward if sender is not native forwarder', async () => { + const attacker = ethers.Wallet.createRandom() + const voteId = 1 + const dandelionVotingInterface = new ethers.Interface([ + 'function delegateVote(address voter, uint256 _voteId, bool _supports)' + ]) + const userData = encode( + ['address[]', 'bytes[]'], + [ + [await voting.getAddress()], + [dandelionVotingInterface.encodeFunctionData('delegateVote', [pntHolder1.address, voteId, true])] + ] + ) + + expect(await pToken.balanceOf(await forwarderHost.getAddress())).to.be.eq(0) + const metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], + [ + '0x02', + getUserDataGeneratedByForwarder(userData, pntHolder1.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + attacker.address, + PNETWORK_NETWORK_IDS.gnosisMainnet, + await forwarderHost.getAddress(), + '0x', + '0x' + ] + ) + + if (_ptokenContract === MOCK_PTOKEN_ERC20) { + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), 1, metadata, '0x')) + .to.emit(pToken, 'ReceiveUserDataFailed') + .and.to.not.emit(voting, 'CastVote') + expect(await pToken.balanceOf(await forwarderHost.getAddress())).to.be.eq(1) + } else if (_ptokenContract === MOCK_PTOKEN_ERC777) { + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), 0, metadata, '0x')) + .to.be.revertedWithCustomError(forwarderHost, 'InvalidOriginAddress') + .withArgs(attacker.address) + expect(await pToken.balanceOf(await forwarderHost.getAddress())).to.be.eq(0) + } else expect.fail('Unsupported pToken contract') }) - it('should be able to change the forwarder after a contract upgrade', async () => { - for (const { artifact, contract } of forwarderRecipientUpgradeableTestData) { - await contract.grantRole(SET_FORWARDER_ROLE, pntHolder1.address) - await contract.connect(pntHolder1).setForwarder(fakeForwarder.address) - expect(await contract.forwarder()).to.be.eq(fakeForwarder.address) - await upgrades.upgradeProxy(await contract.getAddress(), artifact, { - kind: 'uups' - }) - await contract.connect(pntHolder1).setForwarder(await forwarderHost.getAddress()) - expect(await contract.forwarder()).to.be.eq(await forwarderHost.getAddress()) - } + it('should be able to forward a stake request', async () => { + const stakeAmount = ethers.parseEther('1000') + const duration = ONE_DAY * 7 + + const userData = encode( + ['address[]', 'bytes[]'], + [ + [await pToken.getAddress(), await stakingManager.getAddress()], + [ + pToken.interface.encodeFunctionData('approve', [await stakingManager.getAddress(), stakeAmount]), + stakingManager.interface.encodeFunctionData('stake', [pntHolder1.address, stakeAmount, duration]) + ] + ] + ) + + await pnt.connect(pntHolder1).approve(await forwarderNative.getAddress(), stakeAmount) + await forwarderNative + .connect(pntHolder1) + .call(stakeAmount, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) + + // NOTE: at this point let's suppose that a pNetwork node processes the pegin... + + const metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], + [ + '0x02', + getUserDataGeneratedByForwarder(userData, pntHolder1.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + await forwarderNative.getAddress(), + PNETWORK_NETWORK_IDS.gnosisMainnet, + await forwarderHost.getAddress(), + '0x', + '0x' + ] + ) + + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), stakeAmount, metadata, '0x')) + .to.emit(stakingManager, 'Staked') + .withArgs(pntHolder1.address, stakeAmount, duration) }) - }) - it('should be able to forward a vote', async () => { - const voteId = 1 - const dandelionVotingInterface = new ethers.Interface([ - 'function delegateVote(address voter, uint256 _voteId, bool _supports)' - ]) - const userData = encode( - ['address[]', 'bytes[]'], - [ - [await voting.getAddress()], - [dandelionVotingInterface.encodeFunctionData('delegateVote', [pntHolder1.address, voteId, true])] - ] - ) - - await forwarderNative - .connect(pntHolder1) - .call(0, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - - // NOTE: at this point let's suppose that a pNetwork node processes the pegin ... - - const metadata = encode( - ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], - [ - '0x02', - getUserDataGeneratedByForwarder(userData, pntHolder1.address), - PNETWORK_NETWORK_IDS.ethereumMainnet, - await forwarderNative.getAddress(), - PNETWORK_NETWORK_IDS.gnosisMainnet, - await forwarderHost.getAddress(), - '0x', - '0x' - ] - ) + it('should be able to forward a lend request', async () => { + const lendAmount = ethers.parseEther('10000') + const duration = EPOCH_DURATION * 13 - await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), 0, metadata, '0x')) - .to.emit(voting, 'CastVote') - .withArgs(voteId, pntHolder1.address, true) - }) + const userData = encode( + ['address[]', 'bytes[]'], + [ + [await pToken.getAddress(), await lendingManager.getAddress()], + [ + pToken.interface.encodeFunctionData('approve', [await lendingManager.getAddress(), lendAmount]), + lendingManager.interface.encodeFunctionData('lend', [pntHolder1.address, lendAmount, duration]) + ] + ] + ) - it('should not be able to forward if sender is not native forwarder', async () => { - const attacker = ethers.Wallet.createRandom() - const voteId = 1 - const dandelionVotingInterface = new ethers.Interface([ - 'function delegateVote(address voter, uint256 _voteId, bool _supports)' - ]) - const userData = encode( - ['address[]', 'bytes[]'], - [ - [await voting.getAddress()], - [dandelionVotingInterface.encodeFunctionData('delegateVote', [pntHolder1.address, voteId, true])] - ] - ) - - await forwarderNative - .connect(pntHolder1) - .call(0, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - - // NOTE: at this point let's suppose that a pNetwork node processes the pegin ... - - const metadata = encode( - ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], - [ - '0x02', - getUserDataGeneratedByForwarder(userData, pntHolder1.address), - PNETWORK_NETWORK_IDS.ethereumMainnet, - attacker.address, - PNETWORK_NETWORK_IDS.gnosisMainnet, - await forwarderHost.getAddress(), - '0x', - '0x' - ] - ) + await pnt.connect(pntHolder1).approve(await forwarderNative.getAddress(), lendAmount) + await forwarderNative + .connect(pntHolder1) + .call(lendAmount, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), 0, metadata, '0x')) - .to.be.revertedWithCustomError(forwarderNative, 'InvalidOriginAddress') - .withArgs(attacker.address) - }) + // NOTE: at this point let's suppose that a pNetwork node processes the pegin... - it('should be able to forward a stake request', async () => { - const stakeAmount = ethers.parseEther('1000') - const duration = ONE_DAY * 7 + const metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], + [ + '0x02', + getUserDataGeneratedByForwarder(userData, pntHolder1.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + await forwarderNative.getAddress(), + PNETWORK_NETWORK_IDS.gnosisMainnet, + await forwarderHost.getAddress(), + '0x', + '0x' + ] + ) - const userData = encode( - ['address[]', 'bytes[]'], - [ - [await pToken.getAddress(), await stakingManager.getAddress()], + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), lendAmount, metadata, '0x')) + .to.emit(lendingManager, 'Lended') + .withArgs(pntHolder1.address, 1, 12, lendAmount) + }) + + it('should be able to forward a updateSentinelRegistrationByStaking request', async () => { + const stakeAmount = ethers.parseEther('200000') + const duration = EPOCH_DURATION * 13 + const signature = await getSentinelIdentity(pntHolder1.address, { actor: sentinel1, registrationManager }) + + const userData = encode( + ['address[]', 'bytes[]'], [ - pToken.interface.encodeFunctionData('approve', [await stakingManager.getAddress(), stakeAmount]), - stakingManager.interface.encodeFunctionData('stake', [pntHolder1.address, stakeAmount, duration]) + [await pToken.getAddress(), await registrationManager.getAddress()], + [ + pToken.interface.encodeFunctionData('approve', [await registrationManager.getAddress(), stakeAmount]), + registrationManager.interface.encodeFunctionData('updateSentinelRegistrationByStaking', [ + pntHolder1.address, + stakeAmount, + duration, + signature, + 0 + ]) + ] ] - ] - ) - - await pnt.connect(pntHolder1).approve(await forwarderNative.getAddress(), stakeAmount) - await forwarderNative - .connect(pntHolder1) - .call(stakeAmount, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - - // NOTE: at this point let's suppose that a pNetwork node processes the pegin... - - const metadata = encode( - ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], - [ - '0x02', - getUserDataGeneratedByForwarder(userData, pntHolder1.address), - PNETWORK_NETWORK_IDS.ethereumMainnet, - await forwarderNative.getAddress(), - PNETWORK_NETWORK_IDS.gnosisMainnet, - await forwarderHost.getAddress(), - '0x', - '0x' - ] - ) + ) - await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), stakeAmount, metadata, '0x')) - .to.emit(stakingManager, 'Staked') - .withArgs(pntHolder1.address, stakeAmount, duration) - }) + await pnt.connect(pntHolder1).approve(await forwarderNative.getAddress(), stakeAmount) + await forwarderNative + .connect(pntHolder1) + .call(stakeAmount, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - it('should be able to forward a lend request', async () => { - const lendAmount = ethers.parseEther('10000') - const duration = EPOCH_DURATION * 13 + // NOTE: at this point let's suppose that a pNetwork node processes the pegin... - const userData = encode( - ['address[]', 'bytes[]'], - [ - [await pToken.getAddress(), await lendingManager.getAddress()], + const metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], [ - pToken.interface.encodeFunctionData('approve', [await lendingManager.getAddress(), lendAmount]), - lendingManager.interface.encodeFunctionData('lend', [pntHolder1.address, lendAmount, duration]) + '0x02', + getUserDataGeneratedByForwarder(userData, pntHolder1.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + await forwarderNative.getAddress(), + PNETWORK_NETWORK_IDS.gnosisMainnet, + await forwarderHost.getAddress(), + '0x', + '0x' ] - ] - ) - - await pnt.connect(pntHolder1).approve(await forwarderNative.getAddress(), lendAmount) - await forwarderNative - .connect(pntHolder1) - .call(lendAmount, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - - // NOTE: at this point let's suppose that a pNetwork node processes the pegin... - - const metadata = encode( - ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], - [ - '0x02', - getUserDataGeneratedByForwarder(userData, pntHolder1.address), - PNETWORK_NETWORK_IDS.ethereumMainnet, - await forwarderNative.getAddress(), - PNETWORK_NETWORK_IDS.gnosisMainnet, - await forwarderHost.getAddress(), - '0x', - '0x' - ] - ) + ) - await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), lendAmount, metadata, '0x')) - .to.emit(lendingManager, 'Lended') - .withArgs(pntHolder1.address, 1, 12, lendAmount) - }) + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), stakeAmount, metadata, '0x')) + .to.emit(registrationManager, 'SentinelRegistrationUpdated') + .withArgs(pntHolder1.address, 1, 12, sentinel1.address, REGISTRATION_SENTINEL_STAKING, stakeAmount) + }) - it('should be able to forward a updateSentinelRegistrationByStaking request', async () => { - const stakeAmount = ethers.parseEther('200000') - const duration = EPOCH_DURATION * 13 - const signature = await getSentinelIdentity(pntHolder1.address, { actor: sentinel1, registrationManager }) + it('should be able to forward a updateSentinelRegistrationByBorrowing request after a lending one', async () => { + // L E N D + const lendAmount = ethers.parseEther('345678') + const duration = EPOCH_DURATION * 15 - const userData = encode( - ['address[]', 'bytes[]'], - [ - [await pToken.getAddress(), await registrationManager.getAddress()], + let userData = encode( + ['address[]', 'bytes[]'], [ - pToken.interface.encodeFunctionData('approve', [await registrationManager.getAddress(), stakeAmount]), - registrationManager.interface.encodeFunctionData('updateSentinelRegistrationByStaking', [ - pntHolder1.address, - stakeAmount, - duration, - signature, - 0 - ]) + [await pToken.getAddress(), await lendingManager.getAddress()], + [ + pToken.interface.encodeFunctionData('approve', [await lendingManager.getAddress(), lendAmount]), + lendingManager.interface.encodeFunctionData('lend', [pntHolder2.address, lendAmount, duration]) + ] ] - ] - ) - - await pnt.connect(pntHolder1).approve(await forwarderNative.getAddress(), stakeAmount) - await forwarderNative - .connect(pntHolder1) - .call(stakeAmount, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - - // NOTE: at this point let's suppose that a pNetwork node processes the pegin... - - const metadata = encode( - ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], - [ - '0x02', - getUserDataGeneratedByForwarder(userData, pntHolder1.address), - PNETWORK_NETWORK_IDS.ethereumMainnet, - await forwarderNative.getAddress(), - PNETWORK_NETWORK_IDS.gnosisMainnet, - await forwarderHost.getAddress(), - '0x', - '0x' - ] - ) + ) - await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), stakeAmount, metadata, '0x')) - .to.emit(registrationManager, 'SentinelRegistrationUpdated') - .withArgs(pntHolder1.address, 1, 12, sentinel1.address, REGISTRATION_SENTINEL_STAKING, stakeAmount) - }) + await pnt.connect(pntHolder2).approve(await forwarderNative.getAddress(), lendAmount) + await forwarderNative + .connect(pntHolder2) + .call(lendAmount, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) + + let metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], + [ + '0x02', + getUserDataGeneratedByForwarder(userData, pntHolder2.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + await forwarderNative.getAddress(), + PNETWORK_NETWORK_IDS.gnosisMainnet, + await forwarderHost.getAddress(), + '0x', + '0x' + ] + ) + + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), lendAmount, metadata, '0x')) + .to.emit(lendingManager, 'Lended') + .withArgs(pntHolder2.address, 1, 14, lendAmount) - it('should be able to forward a updateSentinelRegistrationByBorrowing request after a lending one', async () => { - // L E N D - const lendAmount = ethers.parseEther('345678') - const duration = EPOCH_DURATION * 15 + // B O R R O W + const numberOfEpochs = 12 + const signature = await getSentinelIdentity(pntHolder1.address, { actor: sentinel1, registrationManager }) - let userData = encode( - ['address[]', 'bytes[]'], - [ - [await pToken.getAddress(), await lendingManager.getAddress()], + userData = encode( + ['address[]', 'bytes[]'], [ - pToken.interface.encodeFunctionData('approve', [await lendingManager.getAddress(), lendAmount]), - lendingManager.interface.encodeFunctionData('lend', [pntHolder2.address, lendAmount, duration]) + [await registrationManager.getAddress()], + [ + registrationManager.interface.encodeFunctionData( + 'updateSentinelRegistrationByBorrowing(address,uint16,bytes,uint256)', + [pntHolder1.address, numberOfEpochs, signature, 0] + ) + ] ] - ] - ) - - await pnt.connect(pntHolder2).approve(await forwarderNative.getAddress(), lendAmount) - await forwarderNative - .connect(pntHolder2) - .call(lendAmount, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - - let metadata = encode( - ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], - [ - '0x02', - getUserDataGeneratedByForwarder(userData, pntHolder2.address), - PNETWORK_NETWORK_IDS.ethereumMainnet, - await forwarderNative.getAddress(), - PNETWORK_NETWORK_IDS.gnosisMainnet, - await forwarderHost.getAddress(), - '0x', - '0x' - ] - ) + ) - await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), lendAmount, metadata, '0x')) - .to.emit(lendingManager, 'Lended') - .withArgs(pntHolder2.address, 1, 14, lendAmount) + await forwarderNative + .connect(pntHolder1) + .call(0, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - // B O R R O W - const numberOfEpochs = 12 - const signature = await getSentinelIdentity(pntHolder1.address, { actor: sentinel1, registrationManager }) + metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], + [ + '0x02', + getUserDataGeneratedByForwarder(userData, pntHolder1.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + await forwarderNative.getAddress(), + PNETWORK_NETWORK_IDS.gnosisMainnet, + await forwarderHost.getAddress(), + '0x', + '0x' + ] + ) - userData = encode( - ['address[]', 'bytes[]'], - [ - [await registrationManager.getAddress()], + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), 0, metadata, '0x')) + .to.emit(registrationManager, 'SentinelRegistrationUpdated') + .withArgs( + pntHolder1.address, + 1, + 12, + sentinel1.address, + REGISTRATION_SENTINEL_BORROWING, + BORROW_AMOUNT_FOR_SENTINEL_REGISTRATION + ) + }) + + it('should be able to forward an unstake request', async () => { + // S T A K E + const amount = ethers.parseEther('10000') + const duration = ONE_DAY * 7 + + let userData = encode( + ['address[]', 'bytes[]'], [ - registrationManager.interface.encodeFunctionData( - 'updateSentinelRegistrationByBorrowing(address,uint16,bytes,uint256)', - [pntHolder1.address, numberOfEpochs, signature, 0] - ) + [await pToken.getAddress(), await stakingManager.getAddress()], + [ + pToken.interface.encodeFunctionData('approve', [await stakingManager.getAddress(), amount]), + stakingManager.interface.encodeFunctionData('stake', [pntHolder1.address, amount, duration]) + ] ] - ] - ) - - await forwarderNative - .connect(pntHolder1) - .call(0, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - - metadata = encode( - ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], - [ - '0x02', - getUserDataGeneratedByForwarder(userData, pntHolder1.address), - PNETWORK_NETWORK_IDS.ethereumMainnet, - await forwarderNative.getAddress(), - PNETWORK_NETWORK_IDS.gnosisMainnet, - await forwarderHost.getAddress(), - '0x', - '0x' - ] - ) - - await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), 0, metadata, '0x')) - .to.emit(registrationManager, 'SentinelRegistrationUpdated') - .withArgs( - pntHolder1.address, - 1, - 12, - sentinel1.address, - REGISTRATION_SENTINEL_BORROWING, - BORROW_AMOUNT_FOR_SENTINEL_REGISTRATION ) - }) - it('should be able to forward an unstake request', async () => { - // S T A K E - const amount = ethers.parseEther('10000') - const duration = ONE_DAY * 7 + await pnt.connect(pntHolder1).approve(await forwarderNative.getAddress(), amount) + await forwarderNative + .connect(pntHolder1) + .call(amount, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - let userData = encode( - ['address[]', 'bytes[]'], - [ - [await pToken.getAddress(), await stakingManager.getAddress()], + let metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], [ - pToken.interface.encodeFunctionData('approve', [await stakingManager.getAddress(), amount]), - stakingManager.interface.encodeFunctionData('stake', [pntHolder1.address, amount, duration]) + '0x02', + getUserDataGeneratedByForwarder(userData, pntHolder1.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + await forwarderNative.getAddress(), + PNETWORK_NETWORK_IDS.gnosisMainnet, + await forwarderHost.getAddress(), + '0x', + '0x' ] - ] - ) - - await pnt.connect(pntHolder1).approve(await forwarderNative.getAddress(), amount) - await forwarderNative - .connect(pntHolder1) - .call(amount, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - - let metadata = encode( - ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], - [ - '0x02', - getUserDataGeneratedByForwarder(userData, pntHolder1.address), - PNETWORK_NETWORK_IDS.ethereumMainnet, - await forwarderNative.getAddress(), - PNETWORK_NETWORK_IDS.gnosisMainnet, - await forwarderHost.getAddress(), - '0x', - '0x' - ] - ) - await pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), amount, metadata, '0x') + ) + await pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), amount, metadata, '0x') - // U N S T A K E (from Ethereum to Gnosis and tokens should come back to ethereum) - await time.increase(duration + 1) + // U N S T A K E (from Ethereum to Gnosis and tokens should come back to ethereum) + await time.increase(duration + 1) - userData = encode( - ['address[]', 'bytes[]'], - [ - [await stakingManager.getAddress()], + userData = encode( + ['address[]', 'bytes[]'], [ - stakingManager.interface.encodeFunctionData('unstake(address,uint256,bytes4)', [ - pntHolder1.address, - amount, - PNETWORK_NETWORK_IDS.ethereumMainnet - ]) + [await stakingManager.getAddress()], + [ + stakingManager.interface.encodeFunctionData('unstake(address,uint256,bytes4)', [ + pntHolder1.address, + amount, + PNETWORK_NETWORK_IDS.ethereumMainnet + ]) + ] ] - ] - ) - - await forwarderNative - .connect(pntHolder1) - .call(0, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - - metadata = encode( - ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], - [ - '0x02', - getUserDataGeneratedByForwarder(userData, pntHolder1.address), - PNETWORK_NETWORK_IDS.ethereumMainnet, - await forwarderNative.getAddress(), - PNETWORK_NETWORK_IDS.gnosisMainnet, - await forwarderHost.getAddress(), - '0x', - '0x' - ] - ) + ) - await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), amount, metadata, '0x')) - .to.emit(stakingManager, 'Unstaked') - .withArgs(pntHolder1.address, amount) - }) + await forwarderNative + .connect(pntHolder1) + .call(0, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) - it('should not be able to updateSentinelRegistrationByBorrowing for a third party', async () => { - await expect( - registrationManager['updateSentinelRegistrationByBorrowing(address,uint16,bytes,uint256)']( - pntHolder2.address, - 2, - '0x', - 0 + metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], + [ + '0x02', + getUserDataGeneratedByForwarder(userData, pntHolder1.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + await forwarderNative.getAddress(), + PNETWORK_NETWORK_IDS.gnosisMainnet, + await forwarderHost.getAddress(), + '0x', + '0x' + ] ) - ).to.be.revertedWithCustomError(registrationManager, 'InvalidForwarder') - }) - it.skip('decode metadata', async () => { - // secretlint-disable-next-line - // https://polygonscan.com/tx/0x25c15710d27d2f7d342ee78ad20c9ce6f4ae9e6f127895b04bf4d67a256050cd - const bytes = + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), amount, metadata, '0x')) + .to.emit(stakingManager, 'Unstaked') + .withArgs(pntHolder1.address, amount) + }) + + it('should not be able to updateSentinelRegistrationByBorrowing for a third party', async () => { + await expect( + registrationManager['updateSentinelRegistrationByBorrowing(address,uint16,bytes,uint256)']( + pntHolder2.address, + 2, + '0x', + 0 + ) + ).to.be.revertedWithCustomError(registrationManager, 'InvalidForwarder') + }) + + it.skip('decode metadata', async () => { // secretlint-disable-next-line - '0x02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100ffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000054d5a0638f23f0b89053f86eed60237bbc56e98c0075dd4c00000000000000000000000000000000000000000000000000000000000000000000000000000000257a984836f4459954ce09955e3c00e8c5b1fb8900000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000728ee450b8c75699149dd297ed6ec4176d8df65e00000000000000000000000067071fc7f4cf8a0fd272d66a5d06fba850198f740000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b6bcae6468760bc0cdfb9c8ef4ee75c9dd23e1ed0000000000000000000000001491733a4c3fa754e895fcd99acdeca0d33645c30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001491733a4c3fa754e895fcd99acdeca0d33645c30000000000000000000000000000000000000000000000af30bbc818391df0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000642b54f55100000000000000000000000067071fc7f4cf8a0fd272d66a5d06fba850198f740000000000000000000000000000000000000000000000af30bbc818391df0000000000000000000000000000000000000000000000000000000000000093a800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - const decoder = new ethers.AbiCoder() - decoder.decode(['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], bytes) + // https://polygonscan.com/tx/0x25c15710d27d2f7d342ee78ad20c9ce6f4ae9e6f127895b04bf4d67a256050cd + const bytes = + // secretlint-disable-next-line + '0x02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100ffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000054d5a0638f23f0b89053f86eed60237bbc56e98c0075dd4c00000000000000000000000000000000000000000000000000000000000000000000000000000000257a984836f4459954ce09955e3c00e8c5b1fb8900000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000728ee450b8c75699149dd297ed6ec4176d8df65e00000000000000000000000067071fc7f4cf8a0fd272d66a5d06fba850198f740000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b6bcae6468760bc0cdfb9c8ef4ee75c9dd23e1ed0000000000000000000000001491733a4c3fa754e895fcd99acdeca0d33645c30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001491733a4c3fa754e895fcd99acdeca0d33645c30000000000000000000000000000000000000000000000af30bbc818391df0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000642b54f55100000000000000000000000067071fc7f4cf8a0fd272d66a5d06fba850198f740000000000000000000000000000000000000000000000af30bbc818391df0000000000000000000000000000000000000000000000000000000000000093a800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + const decoder = new ethers.AbiCoder() + decoder.decode(['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], bytes) + }) }) -}) +) diff --git a/test/fork/dao.test.js b/test/fork/dao.test.js index a63212f..1b5707a 100644 --- a/test/fork/dao.test.js +++ b/test/fork/dao.test.js @@ -83,11 +83,10 @@ describe('Integration tests on Gnosis deployment', () => { await setPermission(DAO_V3_VOTING_CONTRACT, await daoTreasury.getAddress(), TRANSFER_ROLE) await stakingManager.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, DAO_OWNER) await registrationManager.connect(daoOwner).grantRole(UPDATE_GUARDIAN_REGISTRATION_ROLE, DAO_V3_VOTING_CONTRACT) - const MockPToken = await hre.ethers.getContractFactory('MockPToken') + const MockPToken = await hre.ethers.getContractFactory('MockPTokenERC20') pntOnGnosis = await MockPToken.deploy( 'Host Token (pToken)', 'HTKN', - [], pntMinter.address, PNETWORK_NETWORK_IDS.gnosisMainnet ) @@ -301,23 +300,24 @@ describe('Integration tests on Gnosis deployment', () => { 200000000000000000000000n ) }) - - it('should correctly stake after token has been changed', async () => { - const ERC20Factory = await hre.ethers.getContractFactory('MockPToken') - const newToken = await ERC20Factory.deploy('new PNT', 'nPNT', [], faucet.address, '0x00112233') - - await pntOnGnosis.connect(faucet).approve(await stakingManager.getAddress(), 10000) - await mintPntOnGnosis(faucet.address, 10000n) - await stakingManager.connect(faucet).stake(faucet.address, 10000, 86400 * 7) - await expect(stakingManager.connect(daoOwner).changeToken(await newToken.getAddress())) - .to.emit(stakingManager, 'TokenChanged') - .withArgs(await pntOnGnosis.getAddress(), await newToken.getAddress()) - await newToken.connect(faucet).mint(faucet.address, hre.ethers.parseEther('200000'), '0x', '0x') - await newToken.connect(faucet).approve(await stakingManager.getAddress(), 10000) - await expect(stakingManager.connect(faucet).stake(faucet.address, 10000, 86400 * 7)) - .to.be.revertedWithCustomError(stakingManager, 'InvalidToken') - .withArgs(await newToken.getAddress(), await pntOnGnosis.getAddress()) - }) + ;['MockPTokenERC777', 'MockPTokenERC20'].map((_pTokenContract) => + it('should correctly stake after token has been changed', async () => { + const ERC20Factory = await hre.ethers.getContractFactory(_pTokenContract) + const newToken = await ERC20Factory.deploy('new PNT', 'nPNT', faucet.address, '0x00112233') + + await pntOnGnosis.connect(faucet).approve(await stakingManager.getAddress(), 10000) + await mintPntOnGnosis(faucet.address, 10000n) + await stakingManager.connect(faucet).stake(faucet.address, 10000, 86400 * 7) + await expect(stakingManager.connect(daoOwner).changeToken(await newToken.getAddress())) + .to.emit(stakingManager, 'TokenChanged') + .withArgs(await pntOnGnosis.getAddress(), await newToken.getAddress()) + await newToken.connect(faucet).mint(faucet.address, hre.ethers.parseEther('200000'), '0x', '0x') + await newToken.connect(faucet).approve(await stakingManager.getAddress(), 10000) + await expect(stakingManager.connect(faucet).stake(faucet.address, 10000, 86400 * 7)) + .to.be.revertedWithCustomError(stakingManager, 'InvalidToken') + .withArgs(await newToken.getAddress(), await pntOnGnosis.getAddress()) + }) + ) it('should stake and unstake', async () => { await pntOnGnosis.connect(faucet).approve(await stakingManager.getAddress(), 10000) From 81683535ebdd7ad657060004db3105d742f38953 Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Mon, 19 Feb 2024 10:57:40 +0100 Subject: [PATCH 02/18] chore(hardhat): fix private key fetching from .env --- hardhat.config.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/hardhat.config.js b/hardhat.config.js index ba9a462..03cc524 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -27,12 +27,15 @@ const decodeGpgFile = (_file) => encoding: 'utf-8' }).trim() -const maybeGetAccountsFromGpgFile = (_file) => - _file - ? decodeGpgFile(_file) - .then((_key) => [_key]) - .catch((_err) => undefined) - : undefined +const maybeGetAccountsFromGpgFile = (_file) => { + if (!_file) return undefined + try { + const _key = decodeGpgFile(_file) + return [_key] + } catch (_err) { + return undefined + } +} const accounts = maybeGetAccountsFromGpgFile(getEnvironmentVariable('PK')) /** From dccd2e63eaad87af2daff185c7c9959bdc02a4ec Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Mon, 19 Feb 2024 20:20:22 +0100 Subject: [PATCH 03/18] chore(tasks): fix forwarder-deploy tasks --- tasks/deploy_forwarder_bsc.js | 8 ++++---- tasks/deploy_forwarder_gnosis.js | 8 ++++---- tasks/deploy_forwarder_mainnet.js | 6 +++--- tasks/deploy_forwarder_polygon.js | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tasks/deploy_forwarder_bsc.js b/tasks/deploy_forwarder_bsc.js index 9383247..00688f7 100644 --- a/tasks/deploy_forwarder_bsc.js +++ b/tasks/deploy_forwarder_bsc.js @@ -1,12 +1,12 @@ const { task } = require('hardhat/config') -const { PNT_ON_BSC_ADDRESS, ZERO_ADDRESS } = require('../tasks/config') +const { PNT_ON_BSC_ADDRESS } = require('../tasks/config') const deploy = async (_args, _hre) => { - const Forwarder = await _hre.ethers.getContractFactory('Forwarder') + const Forwarder = await _hre.ethers.getContractFactory('ForwarderHost') console.log('Deploying forwarder on BSC ...') - const forwarder = await Forwarder.deploy(PNT_ON_BSC_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS) - console.log('Forwarder deployed at', forwarder.address) + const forwarder = await Forwarder.deploy(PNT_ON_BSC_ADDRESS) + console.log('Forwarder deployed at', await forwarder.getAddress()) } task('deploy:forwarder-bsc', 'Deploy Forwarder on BSC', deploy) diff --git a/tasks/deploy_forwarder_gnosis.js b/tasks/deploy_forwarder_gnosis.js index c0da853..27f6857 100644 --- a/tasks/deploy_forwarder_gnosis.js +++ b/tasks/deploy_forwarder_gnosis.js @@ -1,12 +1,12 @@ const { task } = require('hardhat/config') -const { PNT_ON_GNOSIS_ADDRESS, ZERO_ADDRESS } = require('../tasks/config') +const { PNT_ON_GNOSIS_ADDRESS } = require('../tasks/config') const deploy = async (_args, _hre) => { - const Forwarder = await _hre.ethers.getContractFactory('Forwarder') + const Forwarder = await _hre.ethers.getContractFactory('ForwarderHost') console.log('Deploying forwarder on Gnosis ...') - const forwarder = await Forwarder.deploy(PNT_ON_GNOSIS_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS) - console.log('Forwarder deployed at', forwarder.address) + const forwarder = await Forwarder.deploy(PNT_ON_GNOSIS_ADDRESS) + console.log('Forwarder deployed at', await forwarder.getAddress()) } task('deploy:forwarder-gnosis', 'Deploy Forwarder on Gnosis', deploy) diff --git a/tasks/deploy_forwarder_mainnet.js b/tasks/deploy_forwarder_mainnet.js index 2df63b1..4f70e34 100644 --- a/tasks/deploy_forwarder_mainnet.js +++ b/tasks/deploy_forwarder_mainnet.js @@ -3,10 +3,10 @@ const { task } = require('hardhat/config') const { PNT_ON_ETH_ADDRESS, ERC20_VAULT } = require('../tasks/config') const deploy = async (_args, _hre) => { - const Forwarder = await _hre.ethers.getContractFactory('Forwarder') + const Forwarder = await _hre.ethers.getContractFactory('ForwarderNative') console.log('Deploying forwarder on Ethereum ...') - const forwarder = await Forwarder.deploy(PNT_ON_ETH_ADDRESS, ERC20_VAULT, ERC20_VAULT) - console.log('Forwarder deployed at', forwarder.address) + const forwarder = await Forwarder.deploy(PNT_ON_ETH_ADDRESS, ERC20_VAULT) + console.log('Forwarder deployed at', await forwarder.getAddress()) } task('deploy:forwarder-mainnet', 'Deploy Forwarder on Mainnet', deploy) diff --git a/tasks/deploy_forwarder_polygon.js b/tasks/deploy_forwarder_polygon.js index e1213d9..6d54498 100644 --- a/tasks/deploy_forwarder_polygon.js +++ b/tasks/deploy_forwarder_polygon.js @@ -1,12 +1,12 @@ const { task } = require('hardhat/config') -const { PNT_ON_POLYGON_ADDRESS, ZERO_ADDRESS } = require('../tasks/config') +const { PNT_ON_POLYGON_ADDRESS } = require('../tasks/config') const deploy = async (_args, _hre) => { - const Forwarder = await _hre.ethers.getContractFactory('Forwarder') + const Forwarder = await _hre.ethers.getContractFactory('ForwarderHost') console.log('Deploying forwarder on Polygon ...') - const forwarder = await Forwarder.deploy(PNT_ON_POLYGON_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS) - console.log('Forwarder deployed at', forwarder.address) + const forwarder = await Forwarder.deploy(PNT_ON_POLYGON_ADDRESS) + console.log('Forwarder deployed at', await forwarder.getAddress()) } task('deploy:forwarder-polygon', 'Deploy Forwarder on Polygon', deploy) From 195e562d80c3808b9a973da10c30ecd745015ec8 Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Mon, 19 Feb 2024 20:20:58 +0100 Subject: [PATCH 04/18] chore(hardhat): disable sourcify --- hardhat.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hardhat.config.js b/hardhat.config.js index 03cc524..6a603a2 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -112,6 +112,9 @@ module.exports = { overwrite: false, runOnCompile: false }, + sourcify: { + enabled: false + }, mocha: { timeout: 100000000 } From a90bc1474c64892a1ff02213083be9073c536b72 Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Mon, 19 Feb 2024 20:21:52 +0100 Subject: [PATCH 05/18] chore: export tasks from a single file --- hardhat.config.js | 10 +--------- tasks/index.js | 9 +++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 tasks/index.js diff --git a/hardhat.config.js b/hardhat.config.js index 6a603a2..ffc5543 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -8,15 +8,7 @@ require('@nomicfoundation/hardhat-chai-matchers') require('hardhat-spdx-license-identifier') require('hardhat-tracer') -require('./tasks/decode-forwarder-metadata.js') -require('./tasks/acl-assign-permission.js') -require('./tasks/deploy-dao.js') -require('./tasks/deploy_forwarder_bsc.js') -require('./tasks/deploy_forwarder_gnosis.js') -require('./tasks/deploy_forwarder_mainnet.js') -require('./tasks/deploy_forwarder_polygon.js') -require('./tasks/set_permissions.js') -require('./tasks/upgrade.js') +require('./tasks') const { execSync } = require('child_process') diff --git a/tasks/index.js b/tasks/index.js new file mode 100644 index 0000000..2adb53f --- /dev/null +++ b/tasks/index.js @@ -0,0 +1,9 @@ +require('./decode-forwarder-metadata') +require('./acl-assign-permission') +require('./deploy-dao') +require('./deploy_forwarder_bsc') +require('./deploy_forwarder_gnosis') +require('./deploy_forwarder_mainnet') +require('./deploy_forwarder_polygon') +require('./set_permissions') +require('./upgrade') From 2848a7134ddd0de4ad480cdcce0f99d6790684da Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Mon, 19 Feb 2024 20:23:45 +0100 Subject: [PATCH 06/18] chore(hardhat): fix hardhat-verify configuration --- hardhat.config.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/hardhat.config.js b/hardhat.config.js index ffc5543..a4cf5f7 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -84,18 +84,8 @@ module.exports = { apiKey: { mainnet: getEnvironmentVariable('ETHERSCAN_API_KEY'), polygon: getEnvironmentVariable('POLYGONSCAN_API_KEY'), - gnosis: getEnvironmentVariable('GNOSISSCAN_API_KEY') - }, - customChains: [ - { - network: 'polygon', - chainId: 137, - urls: { - apiURL: 'https://api.polygonscan.com/api', - browserURL: 'https://polygonscan.com' - } - } - ] + xdai: getEnvironmentVariable('GNOSISSCAN_API_KEY') + } }, gasReporter: { enabled: true From 81dd99c986dfd52683fc669a4bb9ff6e3684292a Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Fri, 16 Feb 2024 13:21:34 +0100 Subject: [PATCH 07/18] chore(tasks): add tasks for fetching permissions --- tasks/abi/ACL.json | 773 ++++++++++++++++++++++++++++++++ tasks/check-all-permissions.js | 19 + tasks/check-permissions.js | 41 ++ tasks/index.js | 2 + tasks/lib/roles.js | 37 ++ test/abi/ACL.json | 774 +-------------------------------- 6 files changed, 873 insertions(+), 773 deletions(-) create mode 100644 tasks/abi/ACL.json create mode 100644 tasks/check-all-permissions.js create mode 100644 tasks/check-permissions.js create mode 100644 tasks/lib/roles.js mode change 100644 => 120000 test/abi/ACL.json diff --git a/tasks/abi/ACL.json b/tasks/abi/ACL.json new file mode 100644 index 0000000..014f5f0 --- /dev/null +++ b/tasks/abi/ACL.json @@ -0,0 +1,773 @@ +[ + { + "constant": true, + "inputs": [], + "name": "hasInitialized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + } + ], + "name": "createBurnedPermission", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + } + ], + "name": "burnPermissionManager", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_entity", + "type": "address" + }, + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + } + ], + "name": "grantPermission", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_entity", + "type": "address" + }, + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + } + ], + "name": "getPermissionParamsLength", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_paramsHash", + "type": "bytes32" + }, + { + "name": "_who", + "type": "address" + }, + { + "name": "_where", + "type": "address" + }, + { + "name": "_what", + "type": "bytes32" + }, + { + "name": "_how", + "type": "uint256[]" + } + ], + "name": "evalParams", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "NO_PERMISSION", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_script", + "type": "bytes" + } + ], + "name": "getEVMScriptExecutor", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getRecoveryVault", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "CREATE_PERMISSIONS_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_entity", + "type": "address" + }, + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + }, + { + "name": "_params", + "type": "uint256[]" + } + ], + "name": "grantPermissionP", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_who", + "type": "address" + }, + { + "name": "_where", + "type": "address" + }, + { + "name": "_what", + "type": "bytes32" + } + ], + "name": "hasPermission", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "token", + "type": "address" + } + ], + "name": "allowRecoverability", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "appId", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getInitializationBlock", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_entity", + "type": "address" + }, + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + } + ], + "name": "revokePermission", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + } + ], + "name": "transferToVault", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_entity", + "type": "address" + }, + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + }, + { + "name": "_index", + "type": "uint256" + } + ], + "name": "getPermissionParam", + "outputs": [ + { + "name": "", + "type": "uint8" + }, + { + "name": "", + "type": "uint8" + }, + { + "name": "", + "type": "uint240" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_sender", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + }, + { + "name": "_params", + "type": "uint256[]" + } + ], + "name": "canPerform", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getEVMScriptRegistry", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ANY_ENTITY", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + } + ], + "name": "removePermissionManager", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newManager", + "type": "address" + }, + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + } + ], + "name": "setPermissionManager", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + } + ], + "name": "getPermissionManager", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_entity", + "type": "address" + }, + { + "name": "_app", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + }, + { + "name": "_manager", + "type": "address" + } + ], + "name": "createPermission", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_permissionsCreator", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "EMPTY_PARAM_HASH", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "kernel", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isPetrified", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "BURN_ENTITY", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_who", + "type": "address" + }, + { + "name": "_where", + "type": "address" + }, + { + "name": "_what", + "type": "bytes32" + }, + { + "name": "_how", + "type": "uint256[]" + } + ], + "name": "hasPermission", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_who", + "type": "address" + }, + { + "name": "_where", + "type": "address" + }, + { + "name": "_what", + "type": "bytes32" + }, + { + "name": "_how", + "type": "bytes" + } + ], + "name": "hasPermission", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "entity", + "type": "address" + }, + { + "indexed": true, + "name": "app", + "type": "address" + }, + { + "indexed": true, + "name": "role", + "type": "bytes32" + }, + { + "indexed": false, + "name": "allowed", + "type": "bool" + } + ], + "name": "SetPermission", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "entity", + "type": "address" + }, + { + "indexed": true, + "name": "app", + "type": "address" + }, + { + "indexed": true, + "name": "role", + "type": "bytes32" + }, + { + "indexed": false, + "name": "paramsHash", + "type": "bytes32" + } + ], + "name": "SetPermissionParams", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "app", + "type": "address" + }, + { + "indexed": true, + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "name": "manager", + "type": "address" + } + ], + "name": "ChangePermissionManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "executor", + "type": "address" + }, + { + "indexed": false, + "name": "script", + "type": "bytes" + }, + { + "indexed": false, + "name": "input", + "type": "bytes" + }, + { + "indexed": false, + "name": "returnData", + "type": "bytes" + } + ], + "name": "ScriptResult", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "RecoverToVault", + "type": "event" + } +] \ No newline at end of file diff --git a/tasks/check-all-permissions.js b/tasks/check-all-permissions.js new file mode 100644 index 0000000..06bf15a --- /dev/null +++ b/tasks/check-all-permissions.js @@ -0,0 +1,19 @@ +const { task } = require('hardhat/config') + +const CONFIG = require('./config') + +const checkPermissions = async (_hre, _key, _val) => { + if (_hre.ethers.isAddress(_val) && _val !== CONFIG.ZERO_ADDRESS) { + console.info(`Checking ${_key} # ${_val}`) + await _hre.run('permissions:check', { address: _val }) + console.info('\n') + } +} + +const main = async (_, _hre) => { + for (const entry of Object.entries(CONFIG)) { + await checkPermissions(_hre, entry[0], entry[1]) + } +} + +task('permissions:check-all').setAction(main) diff --git a/tasks/check-permissions.js b/tasks/check-permissions.js new file mode 100644 index 0000000..b1580e1 --- /dev/null +++ b/tasks/check-permissions.js @@ -0,0 +1,41 @@ +const { task } = require('hardhat/config') + +const ACLAbi = require('./abi/ACL.json') +const { ACL_ADDRESS, ZERO_ADDRESS } = require('./config') +const { getAllRoles } = require('./lib/roles') + +const PARAM_CONTRACT_ADDRESS = 'address' + +const checkPermission = async (_contract, _role) => { + const count = await _contract.getRoleMemberCount(_role[1]) + for (let i = 0; i < count; i++) console.info(`${_role[0]}: ${await _contract.getRoleMember(_role[1], i)}`) +} + +const checkAclPermission = async (_acl, _address, _role) => { + const manager = await _acl.getPermissionManager(_address, _role[1]) + if (manager !== ZERO_ADDRESS) console.info(`${_role[0]} manager: ${manager}`) +} + +const tryAccessControlEnumerableUpgradeable = async (_hre, _address, _roles) => { + console.info('Trying AccessControlEnumerableUpgradeable') + const contract = await _hre.ethers.getContractAt('AccessControlEnumerableUpgradeable', _address) + await Promise.all(Object.entries(_roles).map((_role) => checkPermission(contract, _role))) +} + +const tryACL = async (_hre, _address, _roles) => { + console.info('Trying Aragon ACL') + const aclContract = await _hre.ethers.getContractAt(ACLAbi, ACL_ADDRESS) + await Promise.all(Object.entries(_roles).map((_role) => checkAclPermission(aclContract, _address, _role))) +} + +const checkPermissions = async (_params, _hre) => { + const roles = getAllRoles(_hre.ethers) + try { + await tryAccessControlEnumerableUpgradeable(_hre, _params[PARAM_CONTRACT_ADDRESS], roles) + } catch (_err) { + console.info('Failed with AccessControlEnumerableUpgradeable') + await tryACL(_hre, _params[PARAM_CONTRACT_ADDRESS], roles) + } +} + +task('permissions:check').setAction(checkPermissions).addPositionalParam(PARAM_CONTRACT_ADDRESS) diff --git a/tasks/index.js b/tasks/index.js index 2adb53f..2e41499 100644 --- a/tasks/index.js +++ b/tasks/index.js @@ -1,5 +1,7 @@ require('./decode-forwarder-metadata') require('./acl-assign-permission') +require('./check-all-permissions') +require('./check-permissions') require('./deploy-dao') require('./deploy_forwarder_bsc') require('./deploy_forwarder_gnosis') diff --git a/tasks/lib/roles.js b/tasks/lib/roles.js new file mode 100644 index 0000000..6fe9a8c --- /dev/null +++ b/tasks/lib/roles.js @@ -0,0 +1,37 @@ +const R = require('ramda') + +const getRole = R.curry((_ethers, _message) => _ethers.keccak256(_ethers.toUtf8Bytes(_message))) +const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000' +const ROLES = [ + 'BORROW_ROLE', + 'BURN_ROLE', + 'CHANGE_MAX_TOTAL_SUPPLY_ROLE', + 'CHANGE_TOKEN_ROLE', + 'CREATE_PAYMENTS_ROLE', + 'CREATE_VOTES_ROLE', + 'INCREASE_AMOUNT_ROLE', + 'INCREASE_DURATION_ROLE', + 'MINT_ROLE', + 'REDIRECT_CLAIM_TO_CHALLENGER_BY_EPOCH_ROLE', + 'RELEASE_ROLE', + 'SET_FEES_MANAGER_ROLE', + 'SET_FORWARDER_ROLE', + 'SET_GOVERNANCE_MESSAGE_EMITTER_ROLE', + 'SLASH_ROLE', + 'STAKE_ROLE', + 'TRANSFER_ROLE', + 'UPDATE_GUARDIAN_REGISTRATION_ROLE', + 'UPGRADE_ROLE', + 'DEPOSIT_REWARD_ROLE', + 'WITHDRAW_ROLE' +] + +const getAllRoles = (_ethers) => + R.pipe( + R.reduce((acc, currentValue) => R.assoc(currentValue, getRole(_ethers, currentValue), acc), {}), + R.mergeLeft({ DEFAULT_ADMIN_ROLE }) + )(ROLES) + +module.exports = { + getAllRoles +} diff --git a/test/abi/ACL.json b/test/abi/ACL.json deleted file mode 100644 index 014f5f0..0000000 --- a/test/abi/ACL.json +++ /dev/null @@ -1,773 +0,0 @@ -[ - { - "constant": true, - "inputs": [], - "name": "hasInitialized", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - } - ], - "name": "createBurnedPermission", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - } - ], - "name": "burnPermissionManager", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_entity", - "type": "address" - }, - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - } - ], - "name": "grantPermission", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_entity", - "type": "address" - }, - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - } - ], - "name": "getPermissionParamsLength", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_paramsHash", - "type": "bytes32" - }, - { - "name": "_who", - "type": "address" - }, - { - "name": "_where", - "type": "address" - }, - { - "name": "_what", - "type": "bytes32" - }, - { - "name": "_how", - "type": "uint256[]" - } - ], - "name": "evalParams", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "NO_PERMISSION", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_script", - "type": "bytes" - } - ], - "name": "getEVMScriptExecutor", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getRecoveryVault", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "CREATE_PERMISSIONS_ROLE", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_entity", - "type": "address" - }, - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - }, - { - "name": "_params", - "type": "uint256[]" - } - ], - "name": "grantPermissionP", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_who", - "type": "address" - }, - { - "name": "_where", - "type": "address" - }, - { - "name": "_what", - "type": "bytes32" - } - ], - "name": "hasPermission", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "token", - "type": "address" - } - ], - "name": "allowRecoverability", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "appId", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getInitializationBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_entity", - "type": "address" - }, - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - } - ], - "name": "revokePermission", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - } - ], - "name": "transferToVault", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_entity", - "type": "address" - }, - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - }, - { - "name": "_index", - "type": "uint256" - } - ], - "name": "getPermissionParam", - "outputs": [ - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "uint240" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_sender", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - }, - { - "name": "_params", - "type": "uint256[]" - } - ], - "name": "canPerform", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getEVMScriptRegistry", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "ANY_ENTITY", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - } - ], - "name": "removePermissionManager", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_newManager", - "type": "address" - }, - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - } - ], - "name": "setPermissionManager", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - } - ], - "name": "getPermissionManager", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_entity", - "type": "address" - }, - { - "name": "_app", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - }, - { - "name": "_manager", - "type": "address" - } - ], - "name": "createPermission", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_permissionsCreator", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "EMPTY_PARAM_HASH", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "kernel", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isPetrified", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "BURN_ENTITY", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_who", - "type": "address" - }, - { - "name": "_where", - "type": "address" - }, - { - "name": "_what", - "type": "bytes32" - }, - { - "name": "_how", - "type": "uint256[]" - } - ], - "name": "hasPermission", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_who", - "type": "address" - }, - { - "name": "_where", - "type": "address" - }, - { - "name": "_what", - "type": "bytes32" - }, - { - "name": "_how", - "type": "bytes" - } - ], - "name": "hasPermission", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "entity", - "type": "address" - }, - { - "indexed": true, - "name": "app", - "type": "address" - }, - { - "indexed": true, - "name": "role", - "type": "bytes32" - }, - { - "indexed": false, - "name": "allowed", - "type": "bool" - } - ], - "name": "SetPermission", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "entity", - "type": "address" - }, - { - "indexed": true, - "name": "app", - "type": "address" - }, - { - "indexed": true, - "name": "role", - "type": "bytes32" - }, - { - "indexed": false, - "name": "paramsHash", - "type": "bytes32" - } - ], - "name": "SetPermissionParams", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "app", - "type": "address" - }, - { - "indexed": true, - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "name": "manager", - "type": "address" - } - ], - "name": "ChangePermissionManager", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "executor", - "type": "address" - }, - { - "indexed": false, - "name": "script", - "type": "bytes" - }, - { - "indexed": false, - "name": "input", - "type": "bytes" - }, - { - "indexed": false, - "name": "returnData", - "type": "bytes" - } - ], - "name": "ScriptResult", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "vault", - "type": "address" - }, - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - } - ], - "name": "RecoverToVault", - "type": "event" - } -] \ No newline at end of file diff --git a/test/abi/ACL.json b/test/abi/ACL.json new file mode 120000 index 0000000..1575174 --- /dev/null +++ b/test/abi/ACL.json @@ -0,0 +1 @@ +../../tasks/abi/ACL.json \ No newline at end of file From 2392b534187dca2516e9301d772670412150891c Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Mon, 19 Feb 2024 22:14:09 +0100 Subject: [PATCH 08/18] test(fork): update missing steps --- tasks/config.js | 6 +- test/fork/dao.test.js | 128 +++++++++++++++++------------------------- 2 files changed, 56 insertions(+), 78 deletions(-) diff --git a/tasks/config.js b/tasks/config.js index 913f1e6..64f151f 100644 --- a/tasks/config.js +++ b/tasks/config.js @@ -14,6 +14,7 @@ module.exports = { PNT_ON_ETH_ADDRESS: '0x89Ab32156e46F46D02ade3FEcbe5Fc4243B9AAeD', PNT_ON_POLYGON_ADDRESS: '0xb6bcae6468760bc0cdfb9c8ef4ee75c9dd23e1ed', PNT_ON_GNOSIS_ADDRESS: '0x0259461eed4d76d4f0f900f9035f6c4dfb39159a', + DAOPNT_ON_GNOSIS_ADDRESS: '0xFF8Ce5Aca26251Cc3f31e597291c71794C06092a', TOKEN_MANAGER_ADDRESS: '0xCec0058735D50de98d3715792569921FEb9EfDC1', ZERO_ADDRESS: '0x0000000000000000000000000000000000000000', START_FIRST_EPOCH_TIMESTAMP: 1701331199, @@ -24,5 +25,8 @@ module.exports = { LENDING_MANAGER: '0xEf3A54f764F58848e66BaDc427542b44C44b5553', REGISTRATION_MANAGER: '0x08342a325630bE00F55A7Bc5dD64D342B1D3d23D', FEES_MANAGER: '0x053b3d59F06601dF87D9EdD24CB2a81FAE93405f', - REWARDS_MANAGER: '0x2ec44F9F31a55b52b3c1fF98647E38d63f829fb7' + REWARDS_MANAGER: '0x2ec44F9F31a55b52b3c1fF98647E38d63f829fb7', + SAFE_ADDRESS: '0xfE8BCE5b156D9bCD28b5373CDC6b4F08B4b9646a', + FINANCE_VAULT: '0x6239968e6231164687CB40f8389d933dD7f7e0A5', + FINANCE: '0x3d749Bc0eb27795Da58d2f67a2D6527A95567aEC' } diff --git a/test/fork/dao.test.js b/test/fork/dao.test.js index 1b5707a..db30964 100644 --- a/test/fork/dao.test.js +++ b/test/fork/dao.test.js @@ -2,35 +2,30 @@ const { mineUpTo, time } = require('@nomicfoundation/hardhat-network-helpers') const { expect } = require('chai') const hre = require('hardhat') +const { + SAFE_ADDRESS, + STAKING_MANAGER, + STAKING_MANAGER_LM, + STAKING_MANAGER_RM, + LENDING_MANAGER, + DANDELION_VOTING_ADDRESS, + FINANCE_VAULT, + FINANCE, + REGISTRATION_MANAGER, + DAOPNT_ON_GNOSIS_ADDRESS, + ACL_ADDRESS +} = require('../../tasks/config') const AclAbi = require('../abi/ACL.json') const DandelionVotingAbi = require('../abi/DandelionVoting.json') const DaoPntAbi = require('../abi/daoPNT.json') const FinanceAbi = require('../abi/Finance.json') const VaultAbi = require('../abi/Vault.json') const { PNETWORK_NETWORK_IDS } = require('../constants') -const { - MINT_ROLE, - BURN_ROLE, - TRANSFER_ROLE, - CHANGE_TOKEN_ROLE, - UPDATE_GUARDIAN_REGISTRATION_ROLE, - CREATE_VOTES_ROLE, - CREATE_PAYMENTS_ROLE, - UPGRADE_ROLE -} = require('../roles') +const { CHANGE_TOKEN_ROLE, CREATE_VOTES_ROLE, CREATE_PAYMENTS_ROLE, UPGRADE_ROLE } = require('../roles') const { hardhatReset } = require('../utils/hardhat-reset') const { sendEth } = require('../utils/send-eth') // addresses -const ACL_CONTRACT = '0x50b2b8e429cB51bD43cD3E690e5BEB9eb674f6d7' -const DAO_CREATOR = '0x08544a580EDC2C3F27689b740F8257A29166FC77' -const DAO_OWNER = '0x45b5828721453352e7ffd92b8106ca67d0595a26' - -const DAO_PNT = '0xFF8Ce5Aca26251Cc3f31e597291c71794C06092a' -const DAO_V3_VOTING_CONTRACT = '0x0cf759bcCfEf5f322af58ADaE2D28885658B5e02' // gnosis -const DAO_V3_FINANCE_VAULT = '0x6239968e6231164687CB40f8389d933dD7f7e0A5' -const DAO_V3_FINANCE = '0x3d749Bc0eb27795Da58d2f67a2D6527A95567aEC' -const DAO_V3_REGISTRATION_MANAGER = '0x08342a325630bE00F55A7Bc5dD64D342B1D3d23D' const TOKEN_HOLDERS_ADDRESSES = [ '0xc4442915B1FB44972eE4D8404cE05a8D2A1248dA', '0xe8b43e7d55337ab735f6e1932d4a1e98de70eabc', @@ -38,11 +33,6 @@ const TOKEN_HOLDERS_ADDRESSES = [ '0x100a70b9e50e91367d571332e76cfa70e9307059' ] const PNT_ON_GNOSIS_MINTER = '0x53d51f8801f40657ca566a1ae25b27eada97413c' -const DAO_V3_STAKING_MANAGER = '0xdEE8ebE2b7152ECCd935fd67134BF1bad55302BC' -const DAO_V3_STAKING_MANAGER_LM = '0x74107f07765A918890c7a0E9d420Dc587539aD42' -const DAO_V3_STAKING_MANAGER_RM = '0x9ce64A5c880153CD15C097C8D90c39cB073aE945' -const DAO_V3_LENDING_MANAGER = '0xEf3A54f764F58848e66BaDc427542b44C44b5553' -const TOKEN_CONTROLLER = '0xCec0058735D50de98d3715792569921FEb9EfDC1' const USER_ADDRESS = '0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B' @@ -56,7 +46,6 @@ describe('Integration tests on Gnosis deployment', () => { daoVoting, tokenHolders, user, - daoCreator, daoOwner, pntOnGnosis, pntMinter, @@ -74,15 +63,7 @@ describe('Integration tests on Gnosis deployment', () => { finance const missingSteps = async () => { - await setPermission(await stakingManager.getAddress(), TOKEN_CONTROLLER, MINT_ROLE) - await setPermission(await stakingManagerLm.getAddress(), TOKEN_CONTROLLER, MINT_ROLE) - await setPermission(await stakingManagerRm.getAddress(), TOKEN_CONTROLLER, MINT_ROLE) - await setPermission(await stakingManager.getAddress(), TOKEN_CONTROLLER, BURN_ROLE) - await setPermission(await stakingManagerLm.getAddress(), TOKEN_CONTROLLER, BURN_ROLE) - await setPermission(await stakingManagerRm.getAddress(), TOKEN_CONTROLLER, BURN_ROLE) - await setPermission(DAO_V3_VOTING_CONTRACT, await daoTreasury.getAddress(), TRANSFER_ROLE) - await stakingManager.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, DAO_OWNER) - await registrationManager.connect(daoOwner).grantRole(UPDATE_GUARDIAN_REGISTRATION_ROLE, DAO_V3_VOTING_CONTRACT) + await upgradeContracts() const MockPToken = await hre.ethers.getContractFactory('MockPTokenERC20') pntOnGnosis = await MockPToken.deploy( 'Host Token (pToken)', @@ -90,35 +71,29 @@ describe('Integration tests on Gnosis deployment', () => { pntMinter.address, PNETWORK_NETWORK_IDS.gnosisMainnet ) + await stakingManager.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, SAFE_ADDRESS) + await stakingManagerLm.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, SAFE_ADDRESS) + await stakingManagerRm.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, SAFE_ADDRESS) + await lendingManager.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, SAFE_ADDRESS) + await registrationManager.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, SAFE_ADDRESS) + await stakingManager.connect(daoOwner).changeToken(await pntOnGnosis.getAddress()) + await stakingManagerLm.connect(daoOwner).changeToken(await pntOnGnosis.getAddress()) + await stakingManagerRm.connect(daoOwner).changeToken(await pntOnGnosis.getAddress()) + await lendingManager.connect(daoOwner).changeToken(await pntOnGnosis.getAddress()) + await registrationManager.connect(daoOwner).changeToken(await pntOnGnosis.getAddress()) + } + + const upgradeContracts = async () => { await stakingManager.connect(daoOwner).grantRole(UPGRADE_ROLE, faucet.address) await stakingManagerLm.connect(daoOwner).grantRole(UPGRADE_ROLE, faucet.address) await stakingManagerRm.connect(daoOwner).grantRole(UPGRADE_ROLE, faucet.address) await lendingManager.connect(daoOwner).grantRole(UPGRADE_ROLE, faucet.address) await registrationManager.connect(daoOwner).grantRole(UPGRADE_ROLE, faucet.address) - await stakingManager.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, faucet.address) - await stakingManagerLm.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, faucet.address) - await stakingManagerRm.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, faucet.address) - await lendingManager.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, faucet.address) - await registrationManager.connect(daoOwner).grantRole(CHANGE_TOKEN_ROLE, faucet.address) await hre.upgrades.upgradeProxy(stakingManager, StakingManager) await hre.upgrades.upgradeProxy(stakingManagerLm, StakingManagerPermissioned) await hre.upgrades.upgradeProxy(stakingManagerRm, StakingManagerPermissioned) await hre.upgrades.upgradeProxy(lendingManager, LendingManager) await hre.upgrades.upgradeProxy(registrationManager, RegistrationManager) - await stakingManager.changeToken(await pntOnGnosis.getAddress()) - await stakingManagerLm.changeToken(await pntOnGnosis.getAddress()) - await stakingManagerRm.changeToken(await pntOnGnosis.getAddress()) - await lendingManager.changeToken(await pntOnGnosis.getAddress()) - await registrationManager.changeToken(await pntOnGnosis.getAddress()) - } - - const upgradeContracts = async () => { - await stakingManager.connect(daoOwner).grantRole(UPGRADE_ROLE, '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') - await hre.upgrades.upgradeProxy(stakingManager, StakingManager) - await stakingManagerRm.connect(daoOwner).grantRole(UPGRADE_ROLE, '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') - await hre.upgrades.upgradeProxy(stakingManagerRm, StakingManagerPermissioned) - await stakingManagerLm.connect(daoOwner).grantRole(UPGRADE_ROLE, '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') - await hre.upgrades.upgradeProxy(stakingManagerLm, StakingManagerPermissioned) } beforeEach(async () => { @@ -128,8 +103,8 @@ describe('Integration tests on Gnosis deployment', () => { ;[faucet] = await hre.ethers.getSigners() tokenHolders = await Promise.all(TOKEN_HOLDERS_ADDRESSES.map(hre.ethers.getImpersonatedSigner)) user = await hre.ethers.getImpersonatedSigner(USER_ADDRESS) - daoCreator = await hre.ethers.getImpersonatedSigner(DAO_CREATOR) - daoOwner = await hre.ethers.getImpersonatedSigner(DAO_OWNER) + daoOwner = await hre.ethers.getImpersonatedSigner(SAFE_ADDRESS) + await sendEth(hre, faucet, daoOwner.address, '5') pntMinter = await hre.ethers.getImpersonatedSigner(PNT_ON_GNOSIS_MINTER) StakingManager = await hre.ethers.getContractFactory('StakingManager') @@ -137,19 +112,18 @@ describe('Integration tests on Gnosis deployment', () => { RegistrationManager = await hre.ethers.getContractFactory('RegistrationManager') LendingManager = await hre.ethers.getContractFactory('LendingManager') - acl = await hre.ethers.getContractAt(AclAbi, ACL_CONTRACT) - daoVoting = await hre.ethers.getContractAt(DandelionVotingAbi, DAO_V3_VOTING_CONTRACT) - daoTreasury = await hre.ethers.getContractAt(VaultAbi, DAO_V3_FINANCE_VAULT) - finance = await hre.ethers.getContractAt(FinanceAbi, DAO_V3_FINANCE) - daoPNT = await hre.ethers.getContractAt(DaoPntAbi, DAO_PNT) - stakingManager = StakingManager.attach(DAO_V3_STAKING_MANAGER) - stakingManagerLm = StakingManagerPermissioned.attach(DAO_V3_STAKING_MANAGER_LM) - stakingManagerRm = StakingManagerPermissioned.attach(DAO_V3_STAKING_MANAGER_RM) - registrationManager = RegistrationManager.attach(DAO_V3_REGISTRATION_MANAGER) - lendingManager = LendingManager.attach(DAO_V3_LENDING_MANAGER) + acl = await hre.ethers.getContractAt(AclAbi, ACL_ADDRESS) + daoVoting = await hre.ethers.getContractAt(DandelionVotingAbi, DANDELION_VOTING_ADDRESS) + daoTreasury = await hre.ethers.getContractAt(VaultAbi, FINANCE_VAULT) + finance = await hre.ethers.getContractAt(FinanceAbi, FINANCE) + daoPNT = await hre.ethers.getContractAt(DaoPntAbi, DAOPNT_ON_GNOSIS_ADDRESS) + stakingManager = StakingManager.attach(STAKING_MANAGER) + stakingManagerLm = StakingManagerPermissioned.attach(STAKING_MANAGER_LM) + stakingManagerRm = StakingManagerPermissioned.attach(STAKING_MANAGER_RM) + registrationManager = RegistrationManager.attach(REGISTRATION_MANAGER) + lendingManager = LendingManager.attach(LENDING_MANAGER) await missingSteps() - await upgradeContracts() await Promise.all(tokenHolders.map((_holder) => sendEth(hre, faucet, _holder.address, '5'))) await Promise.all(tokenHolders.map((_holder) => mintPntOnGnosis(_holder.address, 10000n))) @@ -183,13 +157,13 @@ describe('Integration tests on Gnosis deployment', () => { const hasPermission = (who, where, what) => acl['hasPermission(address,address,bytes32)'](who, where, what) - const setPermission = async (entity, app, role) => acl.connect(daoCreator).grantPermission(entity, app, role) + const setPermission = async (entity, app, role) => acl.connect(daoOwner).grantPermission(entity, app, role) const grantCreateVotesPermission = async (_who) => { - let hasPerm = await hasPermission(_who, DAO_V3_VOTING_CONTRACT, CREATE_VOTES_ROLE) + let hasPerm = await hasPermission(_who, DANDELION_VOTING_ADDRESS, CREATE_VOTES_ROLE) expect(hasPerm).to.be.false - await setPermission(_who, DAO_V3_VOTING_CONTRACT, CREATE_VOTES_ROLE) - hasPerm = await hasPermission(_who, DAO_V3_VOTING_CONTRACT, CREATE_VOTES_ROLE) + await setPermission(_who, DANDELION_VOTING_ADDRESS, CREATE_VOTES_ROLE) + hasPerm = await hasPermission(_who, DANDELION_VOTING_ADDRESS, CREATE_VOTES_ROLE) expect(hasPerm).to.be.true } @@ -200,7 +174,7 @@ describe('Integration tests on Gnosis deployment', () => { } const stake = async (pntOwner, amount, duration = 604800) => { - await pntOnGnosis.connect(pntOwner).approve(DAO_V3_STAKING_MANAGER, amount) + await pntOnGnosis.connect(pntOwner).approve(STAKING_MANAGER, amount) await stakingManager.connect(pntOwner).stake(pntOwner.address, amount, duration) } @@ -230,9 +204,9 @@ describe('Integration tests on Gnosis deployment', () => { const voteId = 1 const metadata = 'Should we register a new guardian?' const executionScript = encodeCallScript( - [ - [DAO_V3_REGISTRATION_MANAGER, encodeUpdateGuardianRegistrationFunctionData(faucet.address, 10, faucet.address)] - ].map((_args) => encodeFunctionCall(..._args)) + [[REGISTRATION_MANAGER, encodeUpdateGuardianRegistrationFunctionData(faucet.address, 10, faucet.address)]].map( + (_args) => encodeFunctionCall(..._args) + ) ) let currentBlock = await hre.ethers.provider.getBlockNumber() expect(await daoPNT.totalSupplyAt(currentBlock)).to.be.eq(20000) @@ -251,7 +225,7 @@ describe('Integration tests on Gnosis deployment', () => { it('should lend PNTs and register a borrowing sentinel', async () => { const amount = hre.ethers.parseEther('200000', await pntOnGnosis.decimals()) await mintPntOnGnosis(faucet.address, hre.ethers.parseEther('400000', await pntOnGnosis.decimals())) - await pntOnGnosis.connect(faucet).approve(DAO_V3_LENDING_MANAGER, amount) + await pntOnGnosis.connect(faucet).approve(LENDING_MANAGER, amount) const balancePre = await pntOnGnosis.balanceOf(faucet.address) await expect(lendingManager.lend(faucet.address, amount, 86400 * 90)) .to.emit(lendingManager, 'Lended') @@ -284,7 +258,7 @@ describe('Integration tests on Gnosis deployment', () => { await mintPntOnGnosis(user.address, hre.ethers.parseEther('400000', await pntOnGnosis.decimals())) const sentinel = hre.ethers.Wallet.createRandom() const signature = await sentinel.signMessage('test') - await pntOnGnosis.connect(user).approve(DAO_V3_REGISTRATION_MANAGER, amount) + await pntOnGnosis.connect(user).approve(REGISTRATION_MANAGER, amount) expect( await registrationManager .connect(user) @@ -350,7 +324,7 @@ describe('Integration tests on Gnosis deployment', () => { const voteId = 1 const metadata = 'Should we transfer from vault to user?' const executionScript = encodeCallScript( - [[DAO_V3_FINANCE_VAULT, encodeVaultTransfer(await pntOnGnosis.getAddress(), user.address, parseEther('1'))]].map( + [[FINANCE_VAULT, encodeVaultTransfer(await pntOnGnosis.getAddress(), user.address, parseEther('1'))]].map( (_args) => encodeFunctionCall(..._args) ) ) From 7d914adec7f434e47c96001d94025bb61b454395 Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Tue, 20 Feb 2024 12:16:36 +0100 Subject: [PATCH 09/18] feat: add task for getting roles --- tasks/get-roles.js | 9 +++++++++ tasks/index.js | 1 + 2 files changed, 10 insertions(+) create mode 100644 tasks/get-roles.js diff --git a/tasks/get-roles.js b/tasks/get-roles.js new file mode 100644 index 0000000..189f5d3 --- /dev/null +++ b/tasks/get-roles.js @@ -0,0 +1,9 @@ +const { task } = require('hardhat/config') + +const { getAllRoles } = require('./lib/roles') + +const main = (_, _hre) => { + console.log(getAllRoles(_hre.ethers)) +} + +task('get-roles').setAction(main) diff --git a/tasks/index.js b/tasks/index.js index 2e41499..a17c6f8 100644 --- a/tasks/index.js +++ b/tasks/index.js @@ -7,5 +7,6 @@ require('./deploy_forwarder_bsc') require('./deploy_forwarder_gnosis') require('./deploy_forwarder_mainnet') require('./deploy_forwarder_polygon') +require('./get-roles') require('./set_permissions') require('./upgrade') From 7bd5a91ce7132f50b7f995b6ebd66f9c1961dd4c Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Tue, 20 Feb 2024 12:19:47 +0100 Subject: [PATCH 10/18] test(fork): add integration test for forwarders --- tasks/config.js | 4 +- test/abi/PNTonPolygon.json | 1507 ++++++++++++++++++++++++++++++++++ test/fork/forwarders.test.js | 94 +++ 3 files changed, 1603 insertions(+), 2 deletions(-) create mode 100644 test/abi/PNTonPolygon.json create mode 100644 test/fork/forwarders.test.js diff --git a/tasks/config.js b/tasks/config.js index 64f151f..a6fb27f 100644 --- a/tasks/config.js +++ b/tasks/config.js @@ -5,8 +5,8 @@ module.exports = { ERC20_VAULT: '0xe396757EC7E6aC7C8E5ABE7285dde47b98F22db8', FORWARDER_ON_BSC: '0x0000000000000000000000000000000000000000', FORWARDER_ON_MAINNET: '0x0000000000000000000000000000000000000000', - FORWARDER_ON_POLYGON: '0x0000000000000000000000000000000000000000', - FORWARDER_ON_GNOSIS: '0x13D272775434B468D762ce626cafB9276ba94B96', + FORWARDER_ON_POLYGON: '0xC85cd78555DF9991245F15c7AA6c4eDBb7791c19', + FORWARDER_ON_GNOSIS: '0x49157Ddc1cA1907AC7b1f6e871Aa90e93567aDa4', LEND_MAX_EPOCHS: 24, MINIMUM_BORROWING_FEE: 0.3 * 10 ** 6, // 30% PNT_MAX_TOTAL_SUPPLY: '96775228000000000000000000', // 96,775,228 milions diff --git a/test/abi/PNTonPolygon.json b/test/abi/PNTonPolygon.json new file mode 100644 index 0000000..0567460 --- /dev/null +++ b/test/abi/PNTonPolygon.json @@ -0,0 +1,1507 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newOperator", + "type": "address" + } + ], + "name": "AdminOperatorChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "AdminTransferInvoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenHolder", + "type": "address" + } + ], + "name": "AuthorizedOperator", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "operatorData", + "type": "bytes" + } + ], + "name": "Burned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "operatorData", + "type": "bytes" + } + ], + "name": "Minted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "underlyingAssetRecipient", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes4", + "name": "originChainId", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "bytes4", + "name": "destinationChainId", + "type": "bytes4" + } + ], + "name": "Redeem", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldRelayHub", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newRelayHub", + "type": "address" + } + ], + "name": "RelayHubChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenHolder", + "type": "address" + } + ], + "name": "RevokedOperator", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "operatorData", + "type": "bytes" + } + ], + "name": "Sent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MINTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ORIGIN_CHAIN_ID", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_gsnTrustedSigner", + "type": "address" + }, + { + "internalType": "address", + "name": "_gsnFeeTarget", + "type": "address" + } + ], + "name": "__ERC777GSNUpgreadable_init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_adminOperator", + "type": "address" + } + ], + "name": "__ERC777WithAdminOperatorUpgreadable_init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "relay", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "bytes", + "name": "encodedFunction", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "transactionFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "approvalData", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "acceptRelayedCall", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "adminOperator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "operatorData", + "type": "bytes" + } + ], + "name": "adminTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "authorizeOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenHolder", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "_newOriginChainId", + "type": "bytes4" + } + ], + "name": "changeOriginChainId", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "defaultOperators", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getHubAddr", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "grantMinterRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "granularity", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gsnExtraGas", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gsnFeeTarget", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gsnTrustedSigner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "hasMinterRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "tokenName", + "type": "string" + }, + { + "internalType": "string", + "name": "tokenSymbol", + "type": "string" + }, + { + "internalType": "address", + "name": "defaultAdmin", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "originChainId", + "type": "bytes4" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenHolder", + "type": "address" + } + ], + "name": "isOperatorFor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "operatorData", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "operatorData", + "type": "bytes" + } + ], + "name": "operatorBurn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "operatorData", + "type": "bytes" + }, + { + "internalType": "string", + "name": "underlyingAssetRecipient", + "type": "string" + }, + { + "internalType": "bytes4", + "name": "destinationChainId", + "type": "bytes4" + } + ], + "name": "operatorRedeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "operatorData", + "type": "bytes" + } + ], + "name": "operatorSend", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "context", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "actualCharge", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "preRetVal", + "type": "bytes32" + } + ], + "name": "postRelayedCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "context", + "type": "bytes" + } + ], + "name": "preRelayedCall", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "string", + "name": "underlyingAssetRecipient", + "type": "string" + }, + { + "internalType": "bytes4", + "name": "destinationChainId", + "type": "bytes4" + } + ], + "name": "redeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "string", + "name": "underlyingAssetRecipient", + "type": "string" + }, + { + "internalType": "bytes4", + "name": "destinationChainId", + "type": "bytes4" + } + ], + "name": "redeem", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "relayHubVersion", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_account", + "type": "address" + } + ], + "name": "revokeMinterRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "revokeOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "send", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "adminOperator_", + "type": "address" + } + ], + "name": "setAdminOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_gsnFeeTarget", + "type": "address" + } + ], + "name": "setFeeTarget", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_gsnExtraGas", + "type": "uint256" + } + ], + "name": "setGSNExtraGas", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_gsnTrustedSigner", + "type": "address" + } + ], + "name": "setTrustedSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "holder", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/test/fork/forwarders.test.js b/test/fork/forwarders.test.js new file mode 100644 index 0000000..82ddd1d --- /dev/null +++ b/test/fork/forwarders.test.js @@ -0,0 +1,94 @@ +const { expect } = require('chai') +const hre = require('hardhat') + +const { + PNT_ON_POLYGON_ADDRESS, + FORWARDER_ON_GNOSIS, + FORWARDER_ON_POLYGON, + STAKING_MANAGER, + DAOPNT_ON_GNOSIS_ADDRESS +} = require('../../tasks/config') +const pntOnGnosisAbi = require('../abi/PNTonGnosis.json') +const pntOnPolygonAbi = require('../abi/PNTonPolygon.json') +const { PNETWORK_NETWORK_IDS, PNT_ON_GNOSIS_ADDRESS } = require('../constants') +const { encode } = require('../utils') +const { hardhatReset } = require('../utils/hardhat-reset') +const { sendEth } = require('../utils/send-eth') + +describe('Polygon Forwarder', () => { + beforeEach(async () => { + const rpc = hre.config.networks.polygon.url + await hardhatReset(hre.network.provider, rpc) + }) + + it('should call forwarder for staking', async () => { + const [owner] = await hre.ethers.getSigners() + const daoRoot = await hre.ethers.getImpersonatedSigner('0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B') + await sendEth(hre, owner, daoRoot, hre.ethers.parseEther('7')) + await hre.ethers.provider.getBalance('0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B') + const forwarder = await hre.ethers.getContractAt('IForwarder', FORWARDER_ON_POLYGON) + const pToken = await hre.ethers.getContractAt(pntOnPolygonAbi, PNT_ON_POLYGON_ADDRESS) + await expect( + forwarder.connect(daoRoot).call( + '100000000000000000', + FORWARDER_ON_GNOSIS, + // secretlint-disable-next-line + '0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000259461eed4d76d4f0f900f9035f6c4dfb39159a000000000000000000000000dee8ebe2b7152eccd935fd67134bf1bad55302bc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000dee8ebe2b7152eccd935fd67134bf1bad55302bc0000000000000000000000000000000000000000000000000162ea854d0fc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000642b54f551000000000000000000000000ddb5f4535123daa5ae343c24006f4075abaf5f7b0000000000000000000000000000000000000000000000000162ea854d0fc0000000000000000000000000000000000000000000000000000000000000093a8000000000000000000000000000000000000000000000000000000000', + '0x00f1918e' + ) + ) + .to.emit(pToken, 'Redeem') + .withArgs( + await forwarder.getAddress(), + 100000000000000000n, + FORWARDER_ON_GNOSIS.toLowerCase().slice(2), + // secretlint-disable-next-line + '0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ddb5f4535123daa5ae343c24006f4075abaf5f7b0000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000259461eed4d76d4f0f900f9035f6c4dfb39159a000000000000000000000000dee8ebe2b7152eccd935fd67134bf1bad55302bc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000dee8ebe2b7152eccd935fd67134bf1bad55302bc0000000000000000000000000000000000000000000000000162ea854d0fc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000642b54f551000000000000000000000000ddb5f4535123daa5ae343c24006f4075abaf5f7b0000000000000000000000000000000000000000000000000162ea854d0fc0000000000000000000000000000000000000000000000000000000000000093a8000000000000000000000000000000000000000000000000000000000', + '0x00000000', + PNETWORK_NETWORK_IDS.gnosisMainnet + ) + }) +}) + +describe('Gnosis Forwarder', () => { + const missingSteps = async () => { + const owner = await hre.ethers.getImpersonatedSigner('0xfe0BC5fAc8f624093C9CeaeCF1EF14B4a5F84cE9') + const forwarder = await hre.ethers.getContractAt('ForwarderHost', FORWARDER_ON_GNOSIS) + await forwarder.connect(owner).whitelistOriginAddress(FORWARDER_ON_POLYGON) + } + + beforeEach(async () => { + const rpc = hre.config.networks.gnosis.url + await hardhatReset(hre.network.provider, rpc) + await missingSteps() + }) + + it('should stake from forwarder call', async () => { + const USER = '0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B' + const pnetwork = await hre.ethers.getImpersonatedSigner('0x53d51f8801f40657ca566a1ae25b27eada97413c') + const stakingManager = await hre.ethers.getContractAt('StakingManager', STAKING_MANAGER) + await hre.ethers.provider.getBalance('0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B') + const pToken = await hre.ethers.getContractAt(pntOnGnosisAbi, PNT_ON_GNOSIS_ADDRESS) + const daoPNT = await hre.ethers.getContractAt('ERC20', DAOPNT_ON_GNOSIS_ADDRESS) + const metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], + [ + '0x02', + // secretlint-disable-next-line + '0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ddb5f4535123daa5ae343c24006f4075abaf5f7b0000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000259461eed4d76d4f0f900f9035f6c4dfb39159a000000000000000000000000dee8ebe2b7152eccd935fd67134bf1bad55302bc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000dee8ebe2b7152eccd935fd67134bf1bad55302bc0000000000000000000000000000000000000000000000000162ea854d0fc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000642b54f551000000000000000000000000ddb5f4535123daa5ae343c24006f4075abaf5f7b0000000000000000000000000000000000000000000000000162ea854d0fc0000000000000000000000000000000000000000000000000000000000000093a8000000000000000000000000000000000000000000000000000000000', + PNETWORK_NETWORK_IDS.polygonMainnet, + '0xC85cd78555DF9991245F15c7AA6c4eDBb7791c19', + PNETWORK_NETWORK_IDS.gnosisMainnet, + '0x49157Ddc1cA1907AC7b1f6e871Aa90e93567aDa4', + '0x', + '0x' + ] + ) + await expect(pToken.connect(pnetwork).mint(FORWARDER_ON_GNOSIS, 100000000000000000n, metadata, '0x')).to.emit( + stakingManager, + 'Staked' + ) + expect(await daoPNT.balanceOf(USER)).to.be.eq(hre.ethers.parseUnits('0.0999')) + expect(await pToken.balanceOf(STAKING_MANAGER)).to.be.eq(hre.ethers.parseUnits('0.0999')) + }) +}) From a72b21c8ff8adef41a073cbddbfdefb67dcb3b25 Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Tue, 20 Feb 2024 13:01:35 +0100 Subject: [PATCH 11/18] test: use addresses defined in tasks config --- test/FeesManager.test.js | 7 +++---- test/Forwarders.test.js | 9 ++++----- test/LendingManager.test.js | 7 +++---- test/RegistrationManager.test.js | 7 +++---- test/RewardsManager.test.js | 15 ++++----------- test/StakingManager.test.js | 10 ++++------ test/constants.js | 11 ----------- test/fork/forwarders.test.js | 3 ++- 8 files changed, 23 insertions(+), 46 deletions(-) diff --git a/test/FeesManager.test.js b/test/FeesManager.test.js index ae2e81f..b33545b 100644 --- a/test/FeesManager.test.js +++ b/test/FeesManager.test.js @@ -2,16 +2,15 @@ const { time } = require('@nomicfoundation/hardhat-network-helpers') const { expect } = require('chai') const { ethers, upgrades, config, network } = require('hardhat') +const { ACL_ADDRESS, SAFE_ADDRESS, TOKEN_MANAGER_ADDRESS } = require('../tasks/config') + const { - ACL_ADDRESS, - DAO_ROOT_ADDRESS, EPOCH_DURATION, LEND_MAX_EPOCHS, MINIMUM_BORROWING_FEE, PNT_HOLDER_1_ADDRESS, PNT_HOLDER_2_ADDRESS, PNT_MAX_TOTAL_SUPPLY, - TOKEN_MANAGER_ADDRESS, VOTE_STATUS } = require('./constants') const { @@ -87,7 +86,7 @@ describe('FeesManager', () => { guardianOwner2 = signers[13] pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) pntHolder2 = await ethers.getImpersonatedSigner(PNT_HOLDER_2_ADDRESS) - daoRoot = await ethers.getImpersonatedSigner(DAO_ROOT_ADDRESS) + daoRoot = await ethers.getImpersonatedSigner(SAFE_ADDRESS) pnt = await TestToken.deploy('PNT', 'PNT') acl = ACL.attach(ACL_ADDRESS) diff --git a/test/Forwarders.test.js b/test/Forwarders.test.js index 11ead97..6e01123 100644 --- a/test/Forwarders.test.js +++ b/test/Forwarders.test.js @@ -2,10 +2,10 @@ const { time } = require('@nomicfoundation/hardhat-network-helpers') const { expect } = require('chai') const { ethers, upgrades } = require('hardhat') +const { ACL_ADDRESS, SAFE_ADDRESS, TOKEN_MANAGER_ADDRESS } = require('../tasks/config') + const { - ACL_ADDRESS, BORROW_AMOUNT_FOR_SENTINEL_REGISTRATION, - DAO_ROOT_ADDRESS, EPOCH_DURATION, LEND_MAX_EPOCHS, ONE_DAY, @@ -15,8 +15,7 @@ const { PNT_HOLDER_2_ADDRESS, PNT_MAX_TOTAL_SUPPLY, REGISTRATION_SENTINEL_BORROWING, - REGISTRATION_SENTINEL_STAKING, - TOKEN_MANAGER_ADDRESS + REGISTRATION_SENTINEL_STAKING } = require('./constants') const { BORROW_ROLE, @@ -80,7 +79,7 @@ PTOKEN_CONTRACTS.map((_ptokenContract) => pnetwork = await ethers.getImpersonatedSigner(PNETWORK_ADDRESS) pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) pntHolder2 = await ethers.getImpersonatedSigner(PNT_HOLDER_2_ADDRESS) - root = await ethers.getImpersonatedSigner(DAO_ROOT_ADDRESS) + root = await ethers.getImpersonatedSigner(SAFE_ADDRESS) acl = ACL.attach(ACL_ADDRESS) pnt = await TestToken.deploy('PNT', 'PNT') diff --git a/test/LendingManager.test.js b/test/LendingManager.test.js index 10b4735..fd1d84a 100644 --- a/test/LendingManager.test.js +++ b/test/LendingManager.test.js @@ -2,9 +2,9 @@ const { time } = require('@nomicfoundation/hardhat-network-helpers') const { expect } = require('chai') const { ethers, upgrades, config, network } = require('hardhat') +const { ACL_ADDRESS, SAFE_ADDRESS, TOKEN_MANAGER_ADDRESS } = require('../tasks/config') + const { - ACL_ADDRESS, - DAO_ROOT_ADDRESS, EPOCH_DURATION, INFINITE, LEND_MAX_EPOCHS, @@ -13,7 +13,6 @@ const { PNT_HOLDER_1_ADDRESS, PNT_HOLDER_2_ADDRESS, PNT_MAX_TOTAL_SUPPLY, - TOKEN_MANAGER_ADDRESS, VOTE_STATUS } = require('./constants') const { @@ -63,7 +62,7 @@ describe('LendingManager', () => { fakeForwarder = signers[3] pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) pntHolder2 = await ethers.getImpersonatedSigner(PNT_HOLDER_2_ADDRESS) - daoRoot = await ethers.getImpersonatedSigner(DAO_ROOT_ADDRESS) + daoRoot = await ethers.getImpersonatedSigner(SAFE_ADDRESS) pnt = await TestToken.deploy('PNT', 'PNT') acl = ACL.attach(ACL_ADDRESS) diff --git a/test/RegistrationManager.test.js b/test/RegistrationManager.test.js index eeb91b6..868d462 100644 --- a/test/RegistrationManager.test.js +++ b/test/RegistrationManager.test.js @@ -2,10 +2,10 @@ const { time } = require('@nomicfoundation/hardhat-network-helpers') const { expect } = require('chai') const { ethers, upgrades, config, network } = require('hardhat') +const { ACL_ADDRESS, SAFE_ADDRESS, TOKEN_MANAGER_ADDRESS } = require('../tasks/config') + const { - ACL_ADDRESS, BORROW_AMOUNT_FOR_SENTINEL_REGISTRATION, - DAO_ROOT_ADDRESS, EPOCH_DURATION, LEND_MAX_EPOCHS, ONE_DAY, @@ -16,7 +16,6 @@ const { REGISTRATION_SENTINEL_BORROWING, REGISTRATION_SENTINEL_STAKING, REGISTRATON_GUARDIAN, - TOKEN_MANAGER_ADDRESS, MINIMUM_BORROWING_FEE, ONE_HOUR_IN_S } = require('./constants') @@ -97,7 +96,7 @@ describe('RegistrationManager', () => { sentinel2 = signers[11] pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) pntHolder2 = await ethers.getImpersonatedSigner(PNT_HOLDER_2_ADDRESS) - daoRoot = await ethers.getImpersonatedSigner(DAO_ROOT_ADDRESS) + daoRoot = await ethers.getImpersonatedSigner(SAFE_ADDRESS) pnt = await TestToken.deploy('PNT', 'PNT') acl = ACL.attach(ACL_ADDRESS) diff --git a/test/RewardsManager.test.js b/test/RewardsManager.test.js index 89b8628..db44a35 100644 --- a/test/RewardsManager.test.js +++ b/test/RewardsManager.test.js @@ -3,18 +3,11 @@ const { expect } = require('chai') const { ethers, upgrades, config, network } = require('hardhat') const R = require('ramda') +const { ACL_ADDRESS, TOKEN_MANAGER_ADDRESS, SAFE_ADDRESS } = require('../tasks/config') + const AclAbi = require('./abi/ACL.json') const TokenManagerAbi = require('./abi/TokenManager.json') -const { - EPOCH_DURATION, - TOKEN_MANAGER_ADDRESS, - ONE_DAY, - ONE_MONTH, - DAO_CREATOR, - ACL_ADDRESS, - PNT_MAX_TOTAL_SUPPLY, - VOTE_STATUS -} = require('./constants') +const { EPOCH_DURATION, ONE_DAY, ONE_MONTH, PNT_MAX_TOTAL_SUPPLY, VOTE_STATUS } = require('./constants') const { DEPOSIT_REWARD_ROLE, MINT_ROLE, BURN_ROLE, WITHDRAW_ROLE } = require('./roles') const { hardhatReset } = require('./utils/hardhat-reset') @@ -106,7 +99,7 @@ describe('RewardsManager', () => { const signers = await ethers.getSigners() owner = signers[0] - daoCreator = await ethers.getImpersonatedSigner(DAO_CREATOR) + daoCreator = await ethers.getImpersonatedSigner(SAFE_ADDRESS) randomGuy = ethers.Wallet.createRandom().connect(ethers.provider) pntHolder1 = ethers.Wallet.createRandom().connect(ethers.provider) pntHolder2 = ethers.Wallet.createRandom().connect(ethers.provider) diff --git a/test/StakingManager.test.js b/test/StakingManager.test.js index 7c62cfc..3136cda 100644 --- a/test/StakingManager.test.js +++ b/test/StakingManager.test.js @@ -2,16 +2,14 @@ const { time } = require('@nomicfoundation/hardhat-network-helpers') const { expect } = require('chai') const { ethers, upgrades } = require('hardhat') +const { ACL_ADDRESS, DAOPNT_ON_GNOSIS_ADDRESS, SAFE_ADDRESS, TOKEN_MANAGER_ADDRESS } = require('../tasks/config') + const { - ACL_ADDRESS, - DAO_PNT_ADDRESS, - DAO_ROOT_ADDRESS, MIN_LOCK_DURATION, ONE_DAY, PNETWORK_NETWORK_IDS, PNT_HOLDER_1_ADDRESS, PNT_MAX_TOTAL_SUPPLY, - TOKEN_MANAGER_ADDRESS, ZERO_ADDRESS } = require('./constants') const { CHANGE_MAX_TOTAL_SUPPLY_ROLE, UPGRADE_ROLE, SLASH_ROLE, MINT_ROLE, BURN_ROLE } = require('./roles') @@ -30,11 +28,11 @@ describe('StakingManager', () => { fakeForwarder = signers[1] challenger = signers[2] pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) - root = await ethers.getImpersonatedSigner(DAO_ROOT_ADDRESS) + root = await ethers.getImpersonatedSigner(SAFE_ADDRESS) acl = ACL.attach(ACL_ADDRESS) pnt = await TestToken.deploy('PNT', 'PNT') - daoPnt = ERC20.attach(DAO_PNT_ADDRESS) + daoPnt = ERC20.attach(DAOPNT_ON_GNOSIS_ADDRESS) await pnt.connect(owner).transfer(pntHolder1.address, ethers.parseEther('400000')) diff --git a/test/constants.js b/test/constants.js index 7fe9115..93ad997 100644 --- a/test/constants.js +++ b/test/constants.js @@ -1,11 +1,6 @@ module.exports = { - ACL_ADDRESS: '0x50b2b8e429cB51bD43cD3E690e5BEB9eb674f6d7', BORROW_AMOUNT_FOR_SENTINEL_REGISTRATION: '200000000000000000000000', - DANDELION_VOTING_ADDRESS: '0x0cf759bcCfEf5f322af58ADaE2D28885658B5e02', - DAO_PNT_ADDRESS: '0xFF8Ce5Aca26251Cc3f31e597291c71794C06092a', - DAO_ROOT_ADDRESS: '0x08544a580EDC2C3F27689b740F8257A29166FC77', EPOCH_DURATION: 1314001, - ERC20_VAULT: '0xe396757EC7E6aC7C8E5ABE7285dde47b98F22db8', INFINITE: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', LEND_MAX_EPOCHS: 24, MIN_LOCK_DURATION: 604800, @@ -13,7 +8,6 @@ module.exports = { // 30% ONE_DAY: 86400, ONE_MONTH: 86400 * 30, - PBTC_ADDRESS: '0x62199B909FB8B8cf870f97BEf2cE6783493c4908', PNETWORK_ADDRESS: '0x341aA660fD5c280F5a9501E3822bB4a98E816D1b', PNETWORK_NETWORK_IDS: { ethereumMainnet: '0x005fe7f9', @@ -23,8 +17,6 @@ module.exports = { gnosisMainnet: '0x00f1918e', sepolia: '0x953835d9' }, - PNT_ON_ETHEREUM_ADDRESS: '0x89Ab32156e46F46D02ade3FEcbe5Fc4243B9AAeD', - PNT_ON_GNOSIS_ADDRESS: '0x0259461eed4d76d4f0f900f9035f6c4dfb39159a', PNT_HOLDER_1_ADDRESS: '0xaeaa8c6ebb17db8056fa30a08fd3097de555f571', PNT_HOLDER_2_ADDRESS: '0xae0baf66e8f5bb87a6fd54066e469cdfe93212ec', PNT_MAX_TOTAL_SUPPLY: '96775228000000000000000000', // 96,775,228 milions @@ -32,9 +24,6 @@ module.exports = { REGISTRATION_SENTINEL_BORROWING: '0x02', REGISTRATION_SENTINEL_STAKING: '0x01', REGISTRATON_GUARDIAN: '0x03', - TOKEN_MANAGER_ADDRESS: '0xCec0058735D50de98d3715792569921FEb9EfDC1', - DAO_CREATOR: '0x08544a580EDC2C3F27689b740F8257A29166FC77', - ACL_CONTRACT: '0x50b2b8e429cB51bD43cD3E690e5BEB9eb674f6d7', ZERO_ADDRESS: '0x0000000000000000000000000000000000000000', ONE_HOUR_IN_S: 3600, VOTE_STATUS: { diff --git a/test/fork/forwarders.test.js b/test/fork/forwarders.test.js index 82ddd1d..e4b712c 100644 --- a/test/fork/forwarders.test.js +++ b/test/fork/forwarders.test.js @@ -2,6 +2,7 @@ const { expect } = require('chai') const hre = require('hardhat') const { + PNT_ON_GNOSIS_ADDRESS, PNT_ON_POLYGON_ADDRESS, FORWARDER_ON_GNOSIS, FORWARDER_ON_POLYGON, @@ -10,7 +11,7 @@ const { } = require('../../tasks/config') const pntOnGnosisAbi = require('../abi/PNTonGnosis.json') const pntOnPolygonAbi = require('../abi/PNTonPolygon.json') -const { PNETWORK_NETWORK_IDS, PNT_ON_GNOSIS_ADDRESS } = require('../constants') +const { PNETWORK_NETWORK_IDS } = require('../constants') const { encode } = require('../utils') const { hardhatReset } = require('../utils/hardhat-reset') const { sendEth } = require('../utils/send-eth') From 6a5651491ea2e7b23c2fd35b9aee1c73ea1fed6d Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Tue, 20 Feb 2024 13:15:02 +0100 Subject: [PATCH 12/18] test: fix failing tests --- test/FeesManager.test.js | 2 ++ test/Forwarders.test.js | 18 ++++++++++-------- test/LendingManager.test.js | 2 ++ test/RegistrationManager.test.js | 2 ++ test/RewardsManager.test.js | 17 +++++++---------- test/StakingManager.test.js | 12 +++++++----- test/fork/dao.test.js | 4 ++-- test/fork/forwarders.test.js | 2 +- test/utils/send-eth.js | 9 ++++----- 9 files changed, 37 insertions(+), 31 deletions(-) diff --git a/test/FeesManager.test.js b/test/FeesManager.test.js index b33545b..1a19a28 100644 --- a/test/FeesManager.test.js +++ b/test/FeesManager.test.js @@ -25,6 +25,7 @@ const { } = require('./roles') const { getSentinelIdentity } = require('./utils') const { hardhatReset } = require('./utils/hardhat-reset') +const { sendEth } = require('./utils/send-eth') let stakingManagerLM, stakingManagerRM, @@ -87,6 +88,7 @@ describe('FeesManager', () => { pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) pntHolder2 = await ethers.getImpersonatedSigner(PNT_HOLDER_2_ADDRESS) daoRoot = await ethers.getImpersonatedSigner(SAFE_ADDRESS) + sendEth(ethers, owner, daoRoot.address, '1') pnt = await TestToken.deploy('PNT', 'PNT') acl = ACL.attach(ACL_ADDRESS) diff --git a/test/Forwarders.test.js b/test/Forwarders.test.js index 6e01123..2ccac85 100644 --- a/test/Forwarders.test.js +++ b/test/Forwarders.test.js @@ -27,6 +27,7 @@ const { SET_FORWARDER_ROLE } = require('./roles') const { encode, getSentinelIdentity, getUserDataGeneratedByForwarder } = require('./utils') +const { sendEth } = require('./utils/send-eth') let acl, forwarderNative, @@ -44,7 +45,7 @@ let acl, pToken, pnetwork, sentinel1, - root, + daoRoot, pntHolder1, pntHolder2, fakeForwarder, @@ -79,7 +80,8 @@ PTOKEN_CONTRACTS.map((_ptokenContract) => pnetwork = await ethers.getImpersonatedSigner(PNETWORK_ADDRESS) pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) pntHolder2 = await ethers.getImpersonatedSigner(PNT_HOLDER_2_ADDRESS) - root = await ethers.getImpersonatedSigner(SAFE_ADDRESS) + daoRoot = await ethers.getImpersonatedSigner(SAFE_ADDRESS) + sendEth(ethers, owner, daoRoot.address, '1') acl = ACL.attach(ACL_ADDRESS) pnt = await TestToken.deploy('PNT', 'PNT') @@ -165,12 +167,12 @@ PTOKEN_CONTRACTS.map((_ptokenContract) => await voting.setForwarder(await forwarderHost.getAddress()) // set permissions - await acl.connect(root).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) - await acl.connect(root).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) - await acl.connect(root).grantPermission(await stakingManagerRM.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) - await acl.connect(root).grantPermission(await stakingManagerRM.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) - await acl.connect(root).grantPermission(await stakingManagerLM.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) - await acl.connect(root).grantPermission(await stakingManagerLM.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) + await acl.connect(daoRoot).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) + await acl.connect(daoRoot).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) + await acl.connect(daoRoot).grantPermission(await stakingManagerRM.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) + await acl.connect(daoRoot).grantPermission(await stakingManagerRM.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) + await acl.connect(daoRoot).grantPermission(await stakingManagerLM.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) + await acl.connect(daoRoot).grantPermission(await stakingManagerLM.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) await lendingManager.grantRole(BORROW_ROLE, await registrationManager.getAddress()) await stakingManagerLM.grantRole(STAKE_ROLE, await lendingManager.getAddress()) await stakingManagerLM.grantRole(INCREASE_DURATION_ROLE, await lendingManager.getAddress()) diff --git a/test/LendingManager.test.js b/test/LendingManager.test.js index fd1d84a..74b83be 100644 --- a/test/LendingManager.test.js +++ b/test/LendingManager.test.js @@ -26,6 +26,7 @@ const { } = require('./roles') const { truncateWithPrecision } = require('./utils') const { hardhatReset } = require('./utils/hardhat-reset') +const { sendEth } = require('./utils/send-eth') describe('LendingManager', () => { let daoRoot, @@ -63,6 +64,7 @@ describe('LendingManager', () => { pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) pntHolder2 = await ethers.getImpersonatedSigner(PNT_HOLDER_2_ADDRESS) daoRoot = await ethers.getImpersonatedSigner(SAFE_ADDRESS) + sendEth(ethers, owner, daoRoot.address, '1') pnt = await TestToken.deploy('PNT', 'PNT') acl = ACL.attach(ACL_ADDRESS) diff --git a/test/RegistrationManager.test.js b/test/RegistrationManager.test.js index 868d462..049a1df 100644 --- a/test/RegistrationManager.test.js +++ b/test/RegistrationManager.test.js @@ -36,6 +36,7 @@ const { } = require('./roles') const { getSentinelIdentity, truncateWithPrecision } = require('./utils') const { hardhatReset } = require('./utils/hardhat-reset') +const { sendEth } = require('./utils/send-eth') let signers, stakingManagerRM, @@ -97,6 +98,7 @@ describe('RegistrationManager', () => { pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) pntHolder2 = await ethers.getImpersonatedSigner(PNT_HOLDER_2_ADDRESS) daoRoot = await ethers.getImpersonatedSigner(SAFE_ADDRESS) + sendEth(ethers, owner, daoRoot.address, '1') pnt = await TestToken.deploy('PNT', 'PNT') acl = ACL.attach(ACL_ADDRESS) diff --git a/test/RewardsManager.test.js b/test/RewardsManager.test.js index db44a35..9745d5c 100644 --- a/test/RewardsManager.test.js +++ b/test/RewardsManager.test.js @@ -10,6 +10,7 @@ const TokenManagerAbi = require('./abi/TokenManager.json') const { EPOCH_DURATION, ONE_DAY, ONE_MONTH, PNT_MAX_TOTAL_SUPPLY, VOTE_STATUS } = require('./constants') const { DEPOSIT_REWARD_ROLE, MINT_ROLE, BURN_ROLE, WITHDRAW_ROLE } = require('./roles') const { hardhatReset } = require('./utils/hardhat-reset') +const { sendEth } = require('./utils/send-eth') describe('RewardsManager', () => { let epochsManager, @@ -26,15 +27,9 @@ describe('RewardsManager', () => { acl, tokenManager, daoPnt, - daoCreator + daoRoot - const setPermission = async (entity, app, role) => acl.connect(daoCreator).grantPermission(entity, app, role) - - const sendEthers = (_from, _dest, _amount) => - _from.sendTransaction({ - to: _dest.address, - value: ethers.parseEther(_amount) - }) + const setPermission = async (entity, app, role) => acl.connect(daoRoot).grantPermission(entity, app, role) const sendPnt = (_from, _to, _amount) => pnt.connect(_from).transfer(_to, ethers.parseEther(_amount)) @@ -99,7 +94,9 @@ describe('RewardsManager', () => { const signers = await ethers.getSigners() owner = signers[0] - daoCreator = await ethers.getImpersonatedSigner(SAFE_ADDRESS) + daoRoot = await ethers.getImpersonatedSigner(SAFE_ADDRESS) + sendEth(ethers, owner, daoRoot.address, '1') + randomGuy = ethers.Wallet.createRandom().connect(ethers.provider) pntHolder1 = ethers.Wallet.createRandom().connect(ethers.provider) pntHolder2 = ethers.Wallet.createRandom().connect(ethers.provider) @@ -107,7 +104,7 @@ describe('RewardsManager', () => { pntHolder4 = ethers.Wallet.createRandom().connect(ethers.provider) pntHolders = [pntHolder1, pntHolder2, pntHolder3, pntHolder4] - await Promise.all([...pntHolders, daoCreator, randomGuy].map((_dest) => sendEthers(owner, _dest, '1001'))) + await Promise.all([...pntHolders, daoRoot, randomGuy].map((_dest) => sendEth(ethers, owner, _dest.address, '1001'))) acl = await ethers.getContractAt(AclAbi, ACL_ADDRESS) tokenManager = await ethers.getContractAt(TokenManagerAbi, TOKEN_MANAGER_ADDRESS) diff --git a/test/StakingManager.test.js b/test/StakingManager.test.js index 3136cda..8d54226 100644 --- a/test/StakingManager.test.js +++ b/test/StakingManager.test.js @@ -13,9 +13,10 @@ const { ZERO_ADDRESS } = require('./constants') const { CHANGE_MAX_TOTAL_SUPPLY_ROLE, UPGRADE_ROLE, SLASH_ROLE, MINT_ROLE, BURN_ROLE } = require('./roles') +const { sendEth } = require('./utils/send-eth') describe('StakingManager', () => { - let pntHolder1, root, owner, stakingManager, StakingManager, fakeForwarder, challenger, acl, pnt, daoPnt + let pntHolder1, daoRoot, owner, stakingManager, StakingManager, fakeForwarder, challenger, acl, pnt, daoPnt beforeEach(async () => { StakingManager = await ethers.getContractFactory('StakingManager') @@ -28,7 +29,8 @@ describe('StakingManager', () => { fakeForwarder = signers[1] challenger = signers[2] pntHolder1 = await ethers.getImpersonatedSigner(PNT_HOLDER_1_ADDRESS) - root = await ethers.getImpersonatedSigner(SAFE_ADDRESS) + daoRoot = await ethers.getImpersonatedSigner(SAFE_ADDRESS) + sendEth(ethers, owner, daoRoot.address, '1') acl = ACL.attach(ACL_ADDRESS) pnt = await TestToken.deploy('PNT', 'PNT') @@ -46,15 +48,15 @@ describe('StakingManager', () => { ) await owner.sendTransaction({ - to: root.address, + to: daoRoot.address, value: ethers.parseEther('10') }) await stakingManager.grantRole(UPGRADE_ROLE, owner.address) await stakingManager.grantRole(SLASH_ROLE, owner.address) await stakingManager.grantRole(CHANGE_MAX_TOTAL_SUPPLY_ROLE, owner.address) - await acl.connect(root).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) - await acl.connect(root).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) + await acl.connect(daoRoot).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) + await acl.connect(daoRoot).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) await owner.sendTransaction({ to: pntHolder1.address, diff --git a/test/fork/dao.test.js b/test/fork/dao.test.js index db30964..b0095f9 100644 --- a/test/fork/dao.test.js +++ b/test/fork/dao.test.js @@ -104,7 +104,7 @@ describe('Integration tests on Gnosis deployment', () => { tokenHolders = await Promise.all(TOKEN_HOLDERS_ADDRESSES.map(hre.ethers.getImpersonatedSigner)) user = await hre.ethers.getImpersonatedSigner(USER_ADDRESS) daoOwner = await hre.ethers.getImpersonatedSigner(SAFE_ADDRESS) - await sendEth(hre, faucet, daoOwner.address, '5') + await sendEth(hre.ethers, faucet, daoOwner.address, '5') pntMinter = await hre.ethers.getImpersonatedSigner(PNT_ON_GNOSIS_MINTER) StakingManager = await hre.ethers.getContractFactory('StakingManager') @@ -125,7 +125,7 @@ describe('Integration tests on Gnosis deployment', () => { await missingSteps() - await Promise.all(tokenHolders.map((_holder) => sendEth(hre, faucet, _holder.address, '5'))) + await Promise.all(tokenHolders.map((_holder) => sendEth(hre.ethers, faucet, _holder.address, '5'))) await Promise.all(tokenHolders.map((_holder) => mintPntOnGnosis(_holder.address, 10000n))) await Promise.all(tokenHolders.map((_holder) => stake(_holder, 5000))) }) diff --git a/test/fork/forwarders.test.js b/test/fork/forwarders.test.js index e4b712c..16790a6 100644 --- a/test/fork/forwarders.test.js +++ b/test/fork/forwarders.test.js @@ -25,7 +25,7 @@ describe('Polygon Forwarder', () => { it('should call forwarder for staking', async () => { const [owner] = await hre.ethers.getSigners() const daoRoot = await hre.ethers.getImpersonatedSigner('0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B') - await sendEth(hre, owner, daoRoot, hre.ethers.parseEther('7')) + await sendEth(hre.ethers, owner, daoRoot, hre.ethers.parseEther('7')) await hre.ethers.provider.getBalance('0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B') const forwarder = await hre.ethers.getContractAt('IForwarder', FORWARDER_ON_POLYGON) const pToken = await hre.ethers.getContractAt(pntOnPolygonAbi, PNT_ON_POLYGON_ADDRESS) diff --git a/test/utils/send-eth.js b/test/utils/send-eth.js index a0f23da..b74e056 100644 --- a/test/utils/send-eth.js +++ b/test/utils/send-eth.js @@ -1,13 +1,12 @@ const R = require('ramda') -module.exports.sendEth = async (_hre, _from, _to, _eth, _opts = {}) => { +module.exports.sendEth = async (_ethers, _from, _to, _eth, _opts = {}) => { if (R.isNil(_eth)) return Promise.reject(new Error('Unspecified amount:', _eth)) - const value = R.type(_eth) === 'String' ? _hre.ethers.parseEther(_eth) : _eth - const balance = await _hre.ethers.provider.getBalance(await _from.getAddress()) + const value = R.type(_eth) === 'String' ? _ethers.parseEther(_eth) : _eth + const balance = await _ethers.provider.getBalance(await _from.getAddress()) - if (value > balance) - return Promise.reject(new Error(`Failed: insufficient balance ${_hre.ethers.formatEther(balance)}`)) + if (value > balance) return Promise.reject(new Error(`Failed: insufficient balance ${_ethers.formatEther(balance)}`)) return _from.sendTransaction({ ..._opts, to: _to, value }) } From 551732cda3509d287cbddb61344137d131df7e5a Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Tue, 20 Feb 2024 15:01:49 +0100 Subject: [PATCH 13/18] refactor(tasks): add missing roles --- tasks/lib/roles.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tasks/lib/roles.js b/tasks/lib/roles.js index 6fe9a8c..d399627 100644 --- a/tasks/lib/roles.js +++ b/tasks/lib/roles.js @@ -23,7 +23,17 @@ const ROLES = [ 'UPDATE_GUARDIAN_REGISTRATION_ROLE', 'UPGRADE_ROLE', 'DEPOSIT_REWARD_ROLE', - 'WITHDRAW_ROLE' + 'WITHDRAW_ROLE', + 'MODIFY_SUPPORT_ROLE', + 'MODIFY_QUORUM_ROLE', + 'MODIFY_BUFFER_BLOCKS_ROLE', + 'MODIFY_EXECUTION_DELAY_ROLE', + 'MODIFY_MIN_OPEN_VOTE_AMOUNT_ROLE', + 'MODIFY_MIN_OPEN_VOTE_AMOUNT_ROLE', + 'CHANGE_PERIOD_ROLE', + 'CHANGE_BUDGETS_ROLE', + 'EXECUTE_PAYMENTS_ROLE', + 'MANAGE_PAYMENTS_ROLE' ] const getAllRoles = (_ethers) => From dd4f3f157354c7be9f7b4a3d862d98e46bed28da Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Tue, 20 Feb 2024 22:00:49 +0100 Subject: [PATCH 14/18] refactor(forwarders): revise InvalidCaller() event --- contracts/forwarder/ForwarderHost.sol | 4 +- contracts/forwarder/ForwarderNative.sol | 4 +- test/Forwarders.test.js | 57 +++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/contracts/forwarder/ForwarderHost.sol b/contracts/forwarder/ForwarderHost.sol index eff634b..7a55dc6 100644 --- a/contracts/forwarder/ForwarderHost.sol +++ b/contracts/forwarder/ForwarderHost.sol @@ -17,7 +17,7 @@ import {BytesLib} from "../libraries/BytesLib.sol"; error CallFailed(address target, bytes data); error InvalidCallParams(address[] targets, bytes[] data, address caller); error InvalidOriginAddress(address originAddress); -error InvalidCaller(address caller); +error InvalidCaller(address caller, address expected); contract ForwarderHost is IForwarder, IERC777Recipient, Context, Ownable, IPReceiver { using SafeERC20 for IERC20; @@ -93,7 +93,7 @@ contract ForwarderHost is IForwarder, IERC777Recipient, Context, Ownable, IPRece // NOTE: needed to for example avoid someone to vote for someone else if (expectedCaller != caller) { - revert InvalidCaller(expectedCaller); + revert InvalidCaller(caller, expectedCaller); } } diff --git a/contracts/forwarder/ForwarderNative.sol b/contracts/forwarder/ForwarderNative.sol index 99da229..b3e72fd 100644 --- a/contracts/forwarder/ForwarderNative.sol +++ b/contracts/forwarder/ForwarderNative.sol @@ -17,7 +17,7 @@ import {BytesLib} from "../libraries/BytesLib.sol"; error CallFailed(address target, bytes data); error InvalidCallParams(address[] targets, bytes[] data, address caller); error InvalidOriginAddress(address originAddress); -error InvalidCaller(address caller); +error InvalidCaller(address caller, address expected); contract ForwarderNative is IForwarder, IERC777Recipient, Context, Ownable { using SafeERC20 for IERC20; @@ -74,7 +74,7 @@ contract ForwarderNative is IForwarder, IERC777Recipient, Context, Ownable { // NOTE: needed to for example avoid someone to vote for someone else if (expectedCaller != caller) { - revert InvalidCaller(expectedCaller); + revert InvalidCaller(caller, expectedCaller); } } diff --git a/test/Forwarders.test.js b/test/Forwarders.test.js index 2ccac85..bfccb27 100644 --- a/test/Forwarders.test.js +++ b/test/Forwarders.test.js @@ -298,6 +298,63 @@ PTOKEN_CONTRACTS.map((_ptokenContract) => .withArgs(voteId, pntHolder1.address, true) }) + it('should revert if an attacker calls delegateVote', async () => { + const attacker = ethers.Wallet.createRandom().connect(ethers.provider) + await sendEth(ethers, owner, attacker.address, '10') + const voteId = 1 + const dandelionVotingInterface = new ethers.Interface([ + 'function delegateVote(address voter, uint256 _voteId, bool _supports)' + ]) + const userData = encode( + ['address[]', 'bytes[]'], + [ + [await voting.getAddress()], + [dandelionVotingInterface.encodeFunctionData('delegateVote', [pntHolder1.address, voteId, true])] + ] + ) + + await expect( + forwarderNative + .connect(attacker) + .call(0, await forwarderHost.getAddress(), userData, PNETWORK_NETWORK_IDS.gnosisMainnet) + ) + .to.emit(vault, 'PegIn') + .withArgs( + await pnt.getAddress(), + await forwarderNative.getAddress(), + 1, + (await forwarderHost.getAddress()).toLowerCase().slice(2), + getUserDataGeneratedByForwarder(userData, attacker.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + PNETWORK_NETWORK_IDS.gnosisMainnet + ) + + // NOTE: at this point let's suppose that a pNetwork node processes the pegin ... + + const metadata = encode( + ['bytes1', 'bytes', 'bytes4', 'address', 'bytes4', 'address', 'bytes', 'bytes'], + [ + '0x02', + getUserDataGeneratedByForwarder(userData, attacker.address), + PNETWORK_NETWORK_IDS.ethereumMainnet, + await forwarderNative.getAddress(), + PNETWORK_NETWORK_IDS.gnosisMainnet, + await forwarderHost.getAddress(), + '0x', + '0x' + ] + ) + if (_ptokenContract === MOCK_PTOKEN_ERC20) { + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), 0, metadata, '0x')) + .to.emit(pToken, 'ReceiveUserDataFailed') + .and.to.not.emit(voting, 'CastVote') + } else if (_ptokenContract === MOCK_PTOKEN_ERC777) { + await expect(pToken.connect(pnetwork).mint(await forwarderHost.getAddress(), 0, metadata, '0x')) + .to.be.revertedWithCustomError(forwarderHost, 'InvalidCaller') + .withArgs(attacker.address, pntHolder1.address) + } else expect.fail('Unsupported pToken contract') + }) + it('should not be able to forward if sender is not native forwarder', async () => { const attacker = ethers.Wallet.createRandom() const voteId = 1 From eb38a3a1179ee1f97a4057557b19a6c21fdb7755 Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Tue, 20 Feb 2024 22:04:47 +0100 Subject: [PATCH 15/18] test: use sendEth() --- test/FeesManager.test.js | 10 ++-------- test/Forwarders.test.js | 15 +++------------ test/LendingManager.test.js | 10 ++-------- test/RegistrationManager.test.js | 10 ++-------- test/StakingManager.test.js | 10 ++-------- 5 files changed, 11 insertions(+), 44 deletions(-) diff --git a/test/FeesManager.test.js b/test/FeesManager.test.js index 1a19a28..b500e1a 100644 --- a/test/FeesManager.test.js +++ b/test/FeesManager.test.js @@ -180,14 +180,8 @@ describe('FeesManager', () => { await acl.connect(daoRoot).grantPermission(await stakingManagerLM.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) await acl.connect(daoRoot).grantPermission(await stakingManagerLM.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) - await owner.sendTransaction({ - to: pntHolder1.address, - value: ethers.parseEther('10') - }) - await owner.sendTransaction({ - to: pntHolder2.address, - value: ethers.parseEther('10') - }) + await sendEth(ethers, owner, pntHolder1.address, '10') + await sendEth(ethers, owner, pntHolder2.address, '10') }) it('borrower should not be able to earn anything when utilization ratio is 100%', async () => { diff --git a/test/Forwarders.test.js b/test/Forwarders.test.js index bfccb27..239fceb 100644 --- a/test/Forwarders.test.js +++ b/test/Forwarders.test.js @@ -186,18 +186,9 @@ PTOKEN_CONTRACTS.map((_ptokenContract) => await lendingManager.setForwarder(await forwarderHost.getAddress()) await registrationManager.setForwarder(await forwarderHost.getAddress()) - await owner.sendTransaction({ - to: pntHolder1.address, - value: ethers.parseEther('10') - }) - await owner.sendTransaction({ - to: pntHolder2.address, - value: ethers.parseEther('10') - }) - await owner.sendTransaction({ - to: pnetwork.address, - value: ethers.parseEther('10') - }) + await sendEth(ethers, owner, pntHolder1.address, '10') + await sendEth(ethers, owner, pntHolder2.address, '10') + await sendEth(ethers, owner, pnetwork.address, '10') await pnt.connect(owner).transfer(await forwarderNative.getAddress(), ethers.parseEther('10000')) forwarderRecipientUpgradeableTestData = [ diff --git a/test/LendingManager.test.js b/test/LendingManager.test.js index 74b83be..858934b 100644 --- a/test/LendingManager.test.js +++ b/test/LendingManager.test.js @@ -114,14 +114,8 @@ describe('LendingManager', () => { await acl.connect(daoRoot).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) await acl.connect(daoRoot).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) - await owner.sendTransaction({ - to: pntHolder1.address, - value: ethers.parseEther('10') - }) - await owner.sendTransaction({ - to: pntHolder2.address, - value: ethers.parseEther('10') - }) + await sendEth(ethers, owner, pntHolder1.address, '10') + await sendEth(ethers, owner, pntHolder2.address, '10') }) it('should not be able to lend for more than lendMaxEpochs', async () => { diff --git a/test/RegistrationManager.test.js b/test/RegistrationManager.test.js index 049a1df..9bfa721 100644 --- a/test/RegistrationManager.test.js +++ b/test/RegistrationManager.test.js @@ -106,14 +106,8 @@ describe('RegistrationManager', () => { await pnt.connect(owner).transfer(pntHolder1.address, ethers.parseEther('500000')) await pnt.connect(owner).transfer(pntHolder2.address, ethers.parseEther('500000')) - await owner.sendTransaction({ - to: pntHolder1.address, - value: ethers.parseEther('10') - }) - await owner.sendTransaction({ - to: pntHolder2.address, - value: ethers.parseEther('10') - }) + await sendEth(ethers, owner, pntHolder1.address, '10') + await sendEth(ethers, owner, pntHolder2.address, '10') stakingManagerLM = await upgrades.deployProxy( StakingManager, diff --git a/test/StakingManager.test.js b/test/StakingManager.test.js index 8d54226..1a5b911 100644 --- a/test/StakingManager.test.js +++ b/test/StakingManager.test.js @@ -47,10 +47,7 @@ describe('StakingManager', () => { } ) - await owner.sendTransaction({ - to: daoRoot.address, - value: ethers.parseEther('10') - }) + await sendEth(ethers, owner, daoRoot.address, '10') await stakingManager.grantRole(UPGRADE_ROLE, owner.address) await stakingManager.grantRole(SLASH_ROLE, owner.address) @@ -58,10 +55,7 @@ describe('StakingManager', () => { await acl.connect(daoRoot).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, MINT_ROLE) await acl.connect(daoRoot).grantPermission(await stakingManager.getAddress(), TOKEN_MANAGER_ADDRESS, BURN_ROLE) - await owner.sendTransaction({ - to: pntHolder1.address, - value: ethers.parseEther('10') - }) + await sendEth(ethers, owner, pntHolder1.address, '10') }) it('should be able to stake the first time', async () => { From e29c27af0c2364e98016dc1bc4b0654e991fbe66 Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Tue, 20 Feb 2024 22:05:40 +0100 Subject: [PATCH 16/18] test(rewards-manager): remove missingSteps() --- test/RewardsManager.test.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/test/RewardsManager.test.js b/test/RewardsManager.test.js index 9745d5c..dfdd126 100644 --- a/test/RewardsManager.test.js +++ b/test/RewardsManager.test.js @@ -33,11 +33,6 @@ describe('RewardsManager', () => { const sendPnt = (_from, _to, _amount) => pnt.connect(_from).transfer(_to, ethers.parseEther(_amount)) - const missingSteps = async () => { - await setPermission(await rewardsManager.getAddress(), await tokenManager.getAddress(), MINT_ROLE) - await setPermission(await rewardsManager.getAddress(), await tokenManager.getAddress(), BURN_ROLE) - } - const depositRewardsForEpoch = async (_amount, _epoch) => { await rewardsManager.grantRole(DEPOSIT_REWARD_ROLE, owner.address) await pnt.approve(await rewardsManager.getAddress(), _amount) @@ -134,8 +129,8 @@ describe('RewardsManager', () => { kind: 'uups' } ) - - await missingSteps() + await setPermission(await rewardsManager.getAddress(), await tokenManager.getAddress(), MINT_ROLE) + await setPermission(await rewardsManager.getAddress(), await tokenManager.getAddress(), BURN_ROLE) }) it('should deploy correctly', async () => { From 0bb4f5b8cc625512464b7bbdb9757aa34192f73d Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Wed, 21 Feb 2024 10:58:44 +0100 Subject: [PATCH 17/18] feat(tasks): add further permission checks --- tasks/check-permissions.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/tasks/check-permissions.js b/tasks/check-permissions.js index b1580e1..5d9a29f 100644 --- a/tasks/check-permissions.js +++ b/tasks/check-permissions.js @@ -5,15 +5,40 @@ const { ACL_ADDRESS, ZERO_ADDRESS } = require('./config') const { getAllRoles } = require('./lib/roles') const PARAM_CONTRACT_ADDRESS = 'address' +const FIRST_BLOCK = 31195110 +// secretlint-disable-next-line +const SET_PERMISSION_TOPIC = '0x759b9a74d5354b5801710a0c1b283cc9f0d32b607ac8ced10c83ac8e75c77d52' const checkPermission = async (_contract, _role) => { const count = await _contract.getRoleMemberCount(_role[1]) - for (let i = 0; i < count; i++) console.info(`${_role[0]}: ${await _contract.getRoleMember(_role[1], i)}`) + for (let i = 0; i < count; i++) + console.info(`${_role[0]} (${_role[1]}): ${await _contract.getRoleMember(_role[1], i)}`) } -const checkAclPermission = async (_acl, _address, _role) => { +const checkAclPermission = async (_ethers, _acl, _address, _role) => { const manager = await _acl.getPermissionManager(_address, _role[1]) if (manager !== ZERO_ADDRESS) console.info(`${_role[0]} manager: ${manager}`) + const logs = await _ethers.provider.getLogs({ + address: await _acl.getAddress(), + topics: [SET_PERMISSION_TOPIC, null, _ethers.zeroPadValue(_address, 32), _ethers.zeroPadValue(_role[1], 32)], + fromBlock: FIRST_BLOCK + }) + await Promise.all( + logs.map(async (_log) => { + if (await _acl.hasPermission(_ethers.dataSlice(_log.topics[1], 12), _address, _role[1])) + console.info(`${_role[0]} (${_role[1]}): ${_ethers.dataSlice(_log.topics[1], 12)}`) + }) + ) +} + +const tryCheckOwner = async (_hre, _address) => { + const contract = await _hre.ethers.getContractAt('Ownable', _address) + try { + const owner = await contract.owner() + console.info('Owner', owner) + } finally { + // no op + } } const tryAccessControlEnumerableUpgradeable = async (_hre, _address, _roles) => { @@ -25,7 +50,9 @@ const tryAccessControlEnumerableUpgradeable = async (_hre, _address, _roles) => const tryACL = async (_hre, _address, _roles) => { console.info('Trying Aragon ACL') const aclContract = await _hre.ethers.getContractAt(ACLAbi, ACL_ADDRESS) - await Promise.all(Object.entries(_roles).map((_role) => checkAclPermission(aclContract, _address, _role))) + await Promise.all( + Object.entries(_roles).map((_role) => checkAclPermission(_hre.ethers, aclContract, _address, _role)) + ) } const checkPermissions = async (_params, _hre) => { @@ -36,6 +63,7 @@ const checkPermissions = async (_params, _hre) => { console.info('Failed with AccessControlEnumerableUpgradeable') await tryACL(_hre, _params[PARAM_CONTRACT_ADDRESS], roles) } + await tryCheckOwner(_hre, _params[PARAM_CONTRACT_ADDRESS]) } task('permissions:check').setAction(checkPermissions).addPositionalParam(PARAM_CONTRACT_ADDRESS) From 050ef3c7956e19784c479983205c729c77a6af64 Mon Sep 17 00:00:00 2001 From: Alain Olivier Date: Wed, 21 Feb 2024 11:03:07 +0100 Subject: [PATCH 18/18] test(fork): use constants where possible --- test/fork/dao.test.js | 32 ++++++++++++++++---------------- test/fork/forwarders.test.js | 12 ++++++------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/test/fork/dao.test.js b/test/fork/dao.test.js index b0095f9..bbac070 100644 --- a/test/fork/dao.test.js +++ b/test/fork/dao.test.js @@ -243,14 +243,7 @@ describe('Integration tests on Gnosis deployment', () => { ['updateSentinelRegistrationByBorrowing(uint16,bytes,uint256)'](1, signature, 0) ) .to.emit(registrationManager, 'SentinelRegistrationUpdated') - .withArgs( - '0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B', - 2, - 2, - '0xB48299F9704a2A268a09f5d47F56e662624E882f', - 2, - 200000000000000000000000n - ) + .withArgs(USER_ADDRESS, 2, 2, '0xB48299F9704a2A268a09f5d47F56e662624E882f', 2, 200000000000000000000000n) }) it('should register a staking sentinel', async () => { @@ -265,14 +258,7 @@ describe('Integration tests on Gnosis deployment', () => { .updateSentinelRegistrationByStaking(user.address, amount, 86400 * 30, signature, 0) ) .to.emit(registrationManager, 'SentinelRegistrationUpdated') - .withArgs( - '0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B', - 2, - 2, - '0xB48299F9704a2A268a09f5d47F56e662624E882f', - 2, - 200000000000000000000000n - ) + .withArgs(USER_ADDRESS, 2, 2, '0xB48299F9704a2A268a09f5d47F56e662624E882f', 2, 200000000000000000000000n) }) ;['MockPTokenERC777', 'MockPTokenERC20'].map((_pTokenContract) => it('should correctly stake after token has been changed', async () => { @@ -357,4 +343,18 @@ describe('Integration tests on Gnosis deployment', () => { expect(await pntOnGnosis.balanceOf(await daoTreasury.getAddress())).to.be.eq(parseEther('200000') - amount) expect(await pntOnGnosis.balanceOf(user.address)).to.be.eq(amount) }) + + it('should open a vote (1)', async () => { + await setPermission(user.address, await daoVoting.getAddress(), CREATE_VOTES_ROLE) + await expect( + user.sendTransaction({ + to: '0x0cf759bcCfEf5f322af58ADaE2D28885658B5e02', + // secretlint-disable-next-line + data: '0x24160baa00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048746573742068747470733a2f2f697066732e696f2f697066732f516d536e75576d7870744a5a644c4a704b52617278424d53324a75326f414e567267627232785762696539623244000000000000000000000000000000000000000000000000', + value: 0 + }) + ) + .to.emit(daoVoting, 'StartVote') + .withArgs(1, USER_ADDRESS, 'test https://ipfs.io/ipfs/QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D') + }) }) diff --git a/test/fork/forwarders.test.js b/test/fork/forwarders.test.js index 16790a6..3b07c79 100644 --- a/test/fork/forwarders.test.js +++ b/test/fork/forwarders.test.js @@ -35,7 +35,7 @@ describe('Polygon Forwarder', () => { FORWARDER_ON_GNOSIS, // secretlint-disable-next-line '0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000259461eed4d76d4f0f900f9035f6c4dfb39159a000000000000000000000000dee8ebe2b7152eccd935fd67134bf1bad55302bc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000dee8ebe2b7152eccd935fd67134bf1bad55302bc0000000000000000000000000000000000000000000000000162ea854d0fc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000642b54f551000000000000000000000000ddb5f4535123daa5ae343c24006f4075abaf5f7b0000000000000000000000000000000000000000000000000162ea854d0fc0000000000000000000000000000000000000000000000000000000000000093a8000000000000000000000000000000000000000000000000000000000', - '0x00f1918e' + PNETWORK_NETWORK_IDS.gnosisMainnet ) ) .to.emit(pToken, 'Redeem') @@ -65,10 +65,10 @@ describe('Gnosis Forwarder', () => { }) it('should stake from forwarder call', async () => { - const USER = '0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B' + const USER_ADDRESS = '0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B' const pnetwork = await hre.ethers.getImpersonatedSigner('0x53d51f8801f40657ca566a1ae25b27eada97413c') const stakingManager = await hre.ethers.getContractAt('StakingManager', STAKING_MANAGER) - await hre.ethers.provider.getBalance('0xdDb5f4535123DAa5aE343c24006F4075aBAF5F7B') + await hre.ethers.provider.getBalance(USER_ADDRESS) const pToken = await hre.ethers.getContractAt(pntOnGnosisAbi, PNT_ON_GNOSIS_ADDRESS) const daoPNT = await hre.ethers.getContractAt('ERC20', DAOPNT_ON_GNOSIS_ADDRESS) const metadata = encode( @@ -78,9 +78,9 @@ describe('Gnosis Forwarder', () => { // secretlint-disable-next-line '0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ddb5f4535123daa5ae343c24006f4075abaf5f7b0000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000259461eed4d76d4f0f900f9035f6c4dfb39159a000000000000000000000000dee8ebe2b7152eccd935fd67134bf1bad55302bc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000dee8ebe2b7152eccd935fd67134bf1bad55302bc0000000000000000000000000000000000000000000000000162ea854d0fc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000642b54f551000000000000000000000000ddb5f4535123daa5ae343c24006f4075abaf5f7b0000000000000000000000000000000000000000000000000162ea854d0fc0000000000000000000000000000000000000000000000000000000000000093a8000000000000000000000000000000000000000000000000000000000', PNETWORK_NETWORK_IDS.polygonMainnet, - '0xC85cd78555DF9991245F15c7AA6c4eDBb7791c19', + FORWARDER_ON_POLYGON, PNETWORK_NETWORK_IDS.gnosisMainnet, - '0x49157Ddc1cA1907AC7b1f6e871Aa90e93567aDa4', + FORWARDER_ON_GNOSIS, '0x', '0x' ] @@ -89,7 +89,7 @@ describe('Gnosis Forwarder', () => { stakingManager, 'Staked' ) - expect(await daoPNT.balanceOf(USER)).to.be.eq(hre.ethers.parseUnits('0.0999')) + expect(await daoPNT.balanceOf(USER_ADDRESS)).to.be.eq(hre.ethers.parseUnits('0.0999')) expect(await pToken.balanceOf(STAKING_MANAGER)).to.be.eq(hre.ethers.parseUnits('0.0999')) }) })