diff --git a/CHANGELOG.md b/CHANGELOG.md index 9753537c..72e5af36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,25 @@ # Change Log All notable changes to this project will be documented in this file. +## [4.1.4] + +### Added + +- **TREXGateway Enhancements**: + - New function `pause`: Pauses the TREX gateway contract. When paused, no one can deploy a new token. + - New function `unpause`: Unpauses the TREX gateway contract. + + +## [4.1.3] + +### Update + +- **AbstractProxy**: updated the storage slot for `TREXImplementationAuthority` from + `0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7` to + `0x821f3e4d3d679f19eacc940c87acf846ea6eae24a63058ea750304437a62aafc` to avoid issues with blockchain explorers + confusing the proxy pattern of T-REX for an ERC-1822 proxy (old storage slot was the slot used by ERC-1822) which + caused errors in displaying the right ABIs for proxies using this implementation. + ## [4.1.2] - **Compliance Modules**: - Removed `_compliance` parameter from `setSupplyLimit` function of the `SupplyLimitModule` diff --git a/contracts/factory/ITREXGateway.sol b/contracts/factory/ITREXGateway.sol index b7d1da92..a24db14f 100644 --- a/contracts/factory/ITREXGateway.sol +++ b/contracts/factory/ITREXGateway.sol @@ -102,6 +102,12 @@ interface ITREXGateway { /// event emitted whenever a TREX token has been deployed by the TREX factory through the use of the Gateway event GatewaySuiteDeploymentProcessed(address indexed requester, address intendedOwner, uint256 feeApplied); + /// event emitted whenever the gateway is paused by an admin + event GatewayPaused(address _userAddress); + + /// event emitted whenever the gateway is unpaused by an admin + event GatewayUnpaused(address _userAddress); + /// Functions /** @@ -232,6 +238,7 @@ interface ITREXGateway { * The actual TREX suite deployment is then triggered via the factory contract, * and a unique salt is derived from the token owner's address and the token name for the deployment. * + * This function can not be executed if the gateway is paused. * @param _tokenDetails Struct containing details necessary for token deployment such as name, symbol, etc. * @param _claimDetails Struct containing details related to claims for the token. * emits GatewaySuiteDeploymentProcessed This event is emitted post-deployment, indicating the deployer, the token @@ -255,6 +262,7 @@ interface ITREXGateway { * Each TREX suite deployment is triggered via the factory contract, with a * unique salt derived from the token owner's address and token name. * + * This function can not be executed if the gateway is paused. * @param _tokenDetails Array of structs, each containing details necessary for token deployment such as name, symbol, etc. * @param _claimDetails Array of structs, each containing details related to claims for the respective token. * reverts with BatchMaxLengthExceeded if the length of either `_tokenDetails` or `_claimDetails` arrays exceeds 5. @@ -269,6 +277,23 @@ interface ITREXGateway { ITREXFactory.TokenDetails[] memory _tokenDetails, ITREXFactory.ClaimDetails[] memory _claimDetails) external; + /** + * @notice Pauses the gateway contract + * @dev When paused, no one can deploy a new token + * This function can only be called by a gateway agent or the contract owner + * emits a `GatewayPaused` event + */ + function pause() external; + + /** + * @notice Unpauses the gateway contract + * @dev When unpaused, users can deploy new tokens + * if they are registered as deployers or public deployments are allowed + * This function can only be called by a gateway agent or the contract owner + * emits a `GatewayUnpaused` event + */ + function unpause() external; + /** * @notice Retrieves the current public deployment status. * @dev Indicates whether public deployments of TREX contracts are currently allowed. diff --git a/contracts/factory/TREXGateway.sol b/contracts/factory/TREXGateway.sol index 847918fb..43e5d719 100644 --- a/contracts/factory/TREXGateway.sol +++ b/contracts/factory/TREXGateway.sol @@ -102,12 +102,19 @@ error OnlyAdminCall(); /// Batch Size is too big, could run out of gas error BatchMaxLengthExceeded(uint16 lengthLimit); +/// Gateway is paused, you can not call this function +error TREXGatewayIsPaused(); -contract TREXGateway is ITREXGateway, AgentRole { +/// Gateway is not paused, you can not call this function +error TREXGatewayIsNotPaused(); +contract TREXGateway is ITREXGateway, AgentRole { /// address of the TREX Factory that is managed by the Gateway address private _factory; + // emergency-stop variable + bool private _paused = false; + /// public deployment status variable bool private _publicDeploymentStatus; @@ -123,6 +130,22 @@ contract TREXGateway is ITREXGateway, AgentRole { /// mapping for deployment discounts on fees mapping(address => uint16) private _feeDiscount; + /// @dev Modifier to make a function callable only when the contract is not paused. + modifier whenNotPaused() { + if (_paused) { + revert TREXGatewayIsPaused(); + } + _; + } + + /// @dev Modifier to make a function callable only when the contract is paused. + modifier whenPaused() { + if (!_paused) { + revert TREXGatewayIsNotPaused(); + } + _; + } + /// constructor of the contract, setting up the factory address and /// the public deployment status constructor(address factory, bool publicDeploymentStatus) { @@ -136,7 +159,7 @@ contract TREXGateway is ITREXGateway, AgentRole { * @dev See {ITREXGateway-setFactory}. */ function setFactory(address factory) external override onlyOwner { - if(factory == address(0)) { + if (factory == address(0)) { revert ZeroAddress(); } _factory = factory; @@ -147,10 +170,10 @@ contract TREXGateway is ITREXGateway, AgentRole { * @dev See {ITREXGateway-setPublicDeploymentStatus}. */ function setPublicDeploymentStatus(bool _isEnabled) external override onlyOwner { - if(_isEnabled == _publicDeploymentStatus && _isEnabled == true) { + if (_isEnabled == _publicDeploymentStatus && _isEnabled == true) { revert PublicDeploymentAlreadyEnabled(); } - if(_isEnabled == _publicDeploymentStatus && _isEnabled == false) { + if (_isEnabled == _publicDeploymentStatus && _isEnabled == false) { revert PublicDeploymentAlreadyDisabled(); } _publicDeploymentStatus = _isEnabled; @@ -168,10 +191,10 @@ contract TREXGateway is ITREXGateway, AgentRole { * @dev See {ITREXGateway-enableDeploymentFee}. */ function enableDeploymentFee(bool _isEnabled) external override onlyOwner { - if(_isEnabled == _deploymentFeeEnabled && _isEnabled == true) { + if (_isEnabled == _deploymentFeeEnabled && _isEnabled == true) { revert DeploymentFeesAlreadyEnabled(); } - if(_isEnabled == _deploymentFeeEnabled && _isEnabled == false) { + if (_isEnabled == _deploymentFeeEnabled && _isEnabled == false) { revert DeploymentFeesAlreadyDisabled(); } _deploymentFeeEnabled = _isEnabled; @@ -182,7 +205,7 @@ contract TREXGateway is ITREXGateway, AgentRole { * @dev See {ITREXGateway-setDeploymentFee}. */ function setDeploymentFee(uint256 _fee, address _feeToken, address _feeCollector) external override onlyOwner { - if(_feeToken == address(0) || _feeCollector == address(0)) { + if (_feeToken == address(0) || _feeCollector == address(0)) { revert ZeroAddress(); } _deploymentFee.fee = _fee; @@ -195,14 +218,14 @@ contract TREXGateway is ITREXGateway, AgentRole { * @dev See {ITREXGateway-batchAddDeployer}. */ function batchAddDeployer(address[] calldata deployers) external override { - if(!isAgent(msg.sender) && msg.sender != owner()) { + if (!isAgent(msg.sender) && msg.sender != owner()) { revert OnlyAdminCall(); } - if(deployers.length > 500) { + if (deployers.length > 500) { revert BatchMaxLengthExceeded(500); } for (uint256 i = 0; i < deployers.length; i++) { - if(isDeployer(deployers[i])) { + if (isDeployer(deployers[i])) { revert DeployerAlreadyExists(deployers[i]); } _deployers[deployers[i]] = true; @@ -214,10 +237,10 @@ contract TREXGateway is ITREXGateway, AgentRole { * @dev See {ITREXGateway-addDeployer}. */ function addDeployer(address deployer) external override { - if(!isAgent(msg.sender) && msg.sender != owner()) { + if (!isAgent(msg.sender) && msg.sender != owner()) { revert OnlyAdminCall(); } - if(isDeployer(deployer)) { + if (isDeployer(deployer)) { revert DeployerAlreadyExists(deployer); } _deployers[deployer] = true; @@ -228,14 +251,14 @@ contract TREXGateway is ITREXGateway, AgentRole { * @dev See {ITREXGateway-batchRemoveDeployer}. */ function batchRemoveDeployer(address[] calldata deployers) external override { - if(!isAgent(msg.sender) && msg.sender != owner()) { + if (!isAgent(msg.sender) && msg.sender != owner()) { revert OnlyAdminCall(); } - if(deployers.length > 500) { + if (deployers.length > 500) { revert BatchMaxLengthExceeded(500); } for (uint256 i = 0; i < deployers.length; i++) { - if(!isDeployer(deployers[i])) { + if (!isDeployer(deployers[i])) { revert DeployerDoesNotExist(deployers[i]); } delete _deployers[deployers[i]]; @@ -247,10 +270,10 @@ contract TREXGateway is ITREXGateway, AgentRole { * @dev See {ITREXGateway-removeDeployer}. */ function removeDeployer(address deployer) external override { - if(!isAgent(msg.sender) && msg.sender != owner()) { + if (!isAgent(msg.sender) && msg.sender != owner()) { revert OnlyAdminCall(); } - if(!isDeployer(deployer)) { + if (!isDeployer(deployer)) { revert DeployerDoesNotExist(deployer); } delete _deployers[deployer]; @@ -261,14 +284,14 @@ contract TREXGateway is ITREXGateway, AgentRole { * @dev See {ITREXGateway-batchApplyFeeDiscount}. */ function batchApplyFeeDiscount(address[] calldata deployers, uint16[] calldata discounts) external override { - if(!isAgent(msg.sender) && msg.sender != owner()) { + if (!isAgent(msg.sender) && msg.sender != owner()) { revert OnlyAdminCall(); } - if(deployers.length > 500) { + if (deployers.length > 500) { revert BatchMaxLengthExceeded(500); } for (uint256 i = 0; i < deployers.length; i++) { - if(discounts[i] > 10000) { + if (discounts[i] > 10000) { revert DiscountOutOfRange(); } _feeDiscount[deployers[i]] = discounts[i]; @@ -280,10 +303,10 @@ contract TREXGateway is ITREXGateway, AgentRole { * @dev See {ITREXGateway-applyFeeDiscount}. */ function applyFeeDiscount(address deployer, uint16 discount) external override { - if(!isAgent(msg.sender) && msg.sender != owner()) { + if (!isAgent(msg.sender) && msg.sender != owner()) { revert OnlyAdminCall(); } - if(discount > 10000) { + if (discount > 10000) { revert DiscountOutOfRange(); } _feeDiscount[deployer] = discount; @@ -295,9 +318,9 @@ contract TREXGateway is ITREXGateway, AgentRole { */ function batchDeployTREXSuite( ITREXFactory.TokenDetails[] memory _tokenDetails, - ITREXFactory.ClaimDetails[] memory _claimDetails) external override - { - if(_tokenDetails.length > 5) { + ITREXFactory.ClaimDetails[] memory _claimDetails + ) external override whenNotPaused { + if (_tokenDetails.length > 5) { revert BatchMaxLengthExceeded(5); } for (uint256 i = 0; i < _tokenDetails.length; i++) { @@ -305,57 +328,79 @@ contract TREXGateway is ITREXGateway, AgentRole { } } + /** + * @dev See {ITREXGateway-pause}. + */ + function pause() external override whenNotPaused { + if (!isAgent(msg.sender) && msg.sender != owner()) { + revert OnlyAdminCall(); + } + + _paused = true; + emit GatewayPaused(msg.sender); + } + + /** + * @dev See {ITREXGateway-unpause}. + */ + function unpause() external override whenPaused { + if (!isAgent(msg.sender) && msg.sender != owner()) { + revert OnlyAdminCall(); + } + + _paused = false; + emit GatewayUnpaused(msg.sender); + } + /** * @dev See {ITREXGateway-getPublicDeploymentStatus}. */ - function getPublicDeploymentStatus() external override view returns(bool) { + function getPublicDeploymentStatus() external view override returns (bool) { return _publicDeploymentStatus; } /** * @dev See {ITREXGateway-getFactory}. */ - function getFactory() external override view returns(address) { + function getFactory() external view override returns (address) { return _factory; } /** * @dev See {ITREXGateway-getDeploymentFee}. */ - function getDeploymentFee() external override view returns(Fee memory) { + function getDeploymentFee() external view override returns (Fee memory) { return _deploymentFee; } /** * @dev See {ITREXGateway-isDeploymentFeeEnabled}. */ - function isDeploymentFeeEnabled() external override view returns(bool) { + function isDeploymentFeeEnabled() external view override returns (bool) { return _deploymentFeeEnabled; } /** * @dev See {ITREXGateway-deployTREXSuite}. */ - function deployTREXSuite(ITREXFactory.TokenDetails memory _tokenDetails, ITREXFactory.ClaimDetails memory _claimDetails) - public override { - if(_publicDeploymentStatus == false && !isDeployer(msg.sender)) { + function deployTREXSuite( + ITREXFactory.TokenDetails memory _tokenDetails, + ITREXFactory.ClaimDetails memory _claimDetails + ) public override whenNotPaused { + if (_publicDeploymentStatus == false && !isDeployer(msg.sender)) { revert PublicDeploymentsNotAllowed(); } - if(_publicDeploymentStatus == true && msg.sender != _tokenDetails.owner && !isDeployer(msg.sender)) { + if (_publicDeploymentStatus == true && msg.sender != _tokenDetails.owner && !isDeployer(msg.sender)) { revert PublicCannotDeployOnBehalf(); } uint256 feeApplied = 0; - if(_deploymentFeeEnabled == true) { - if(_deploymentFee.fee > 0 && _feeDiscount[msg.sender] < 10000) { + if (_deploymentFeeEnabled == true) { + if (_deploymentFee.fee > 0 && _feeDiscount[msg.sender] < 10000) { feeApplied = calculateFee(msg.sender); - IERC20(_deploymentFee.feeToken).transferFrom( - msg.sender, - _deploymentFee.feeCollector, - feeApplied - ); + IERC20(_deploymentFee.feeToken).transferFrom(msg.sender, _deploymentFee.feeCollector, feeApplied); } } - string memory _salt = string(abi.encodePacked(Strings.toHexString(_tokenDetails.owner), _tokenDetails.name)); + string memory _salt = string(abi.encodePacked(Strings.toHexString(_tokenDetails.owner), _tokenDetails.name)); ITREXFactory(_factory).deployTREXSuite(_salt, _tokenDetails, _claimDetails); emit GatewaySuiteDeploymentProcessed(msg.sender, _tokenDetails.owner, feeApplied); } @@ -363,14 +408,14 @@ contract TREXGateway is ITREXGateway, AgentRole { /** * @dev See {ITREXGateway-isDeployer}. */ - function isDeployer(address deployer) public override view returns(bool) { + function isDeployer(address deployer) public view override returns (bool) { return _deployers[deployer]; } /** * @dev See {ITREXGateway-calculateFee}. */ - function calculateFee(address deployer) public override view returns(uint256) { + function calculateFee(address deployer) public view override returns (uint256) { return _deploymentFee.fee - ((_feeDiscount[deployer] * _deploymentFee.fee) / 10000); } } diff --git a/contracts/proxy/AbstractProxy.sol b/contracts/proxy/AbstractProxy.sol index 90af9f34..1844e7b4 100644 --- a/contracts/proxy/AbstractProxy.sol +++ b/contracts/proxy/AbstractProxy.sol @@ -93,18 +93,19 @@ abstract contract AbstractProxy is IProxy, Initializable { address implemAuth; // solhint-disable-next-line no-inline-assembly assembly { - implemAuth := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) + implemAuth := sload(0x821f3e4d3d679f19eacc940c87acf846ea6eae24a63058ea750304437a62aafc) } return implemAuth; } /** - * @dev store the implementationAuthority contract address using the ERC-1822 implementation slot in storage + * @dev store the implementationAuthority contract address using the ERC-3643 implementation slot in storage + * the slot storage is the result of `keccak256("ERC-3643.proxy.beacon")` */ function _storeImplementationAuthority(address implementationAuthority) internal { // solhint-disable-next-line no-inline-assembly assembly { - sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, implementationAuthority) + sstore(0x821f3e4d3d679f19eacc940c87acf846ea6eae24a63058ea750304437a62aafc, implementationAuthority) } } diff --git a/contracts/token/TokenStorage.sol b/contracts/token/TokenStorage.sol index 046f44bc..1217bcde 100644 --- a/contracts/token/TokenStorage.sol +++ b/contracts/token/TokenStorage.sol @@ -76,7 +76,7 @@ contract TokenStorage { string internal _tokenSymbol; uint8 internal _tokenDecimals; address internal _tokenOnchainID; - string internal constant _TOKEN_VERSION = "4.1.1"; + string internal constant _TOKEN_VERSION = "4.1.4"; /// @dev Variables of freeze and pause functions mapping(address => bool) internal _frozen; diff --git a/package.json b/package.json index d354820d..d0846aab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tokenysolutions/t-rex", - "version": "4.1.2", + "version": "4.1.4", "description": "A fully compliant environment for the issuance and use of tokenized securities.", "main": "index.js", "directories": { diff --git a/test/gateway.test.ts b/test/gateway.test.ts index 0c5113b6..c87c27f6 100644 --- a/test/gateway.test.ts +++ b/test/gateway.test.ts @@ -674,6 +674,37 @@ describe('TREXGateway', () => { }); }); describe('.deployTREXSuite()', () => { + describe('when contract is paused', () => { + it('should revert', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [context.factories.trexFactory.address, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + await gateway.pause(); + + await expect( + gateway.connect(context.accounts.anotherWallet).deployTREXSuite( + { + owner: context.accounts.anotherWallet.address, + name: 'Token name', + symbol: 'SYM', + decimals: 8, + irs: ethers.constants.AddressZero, + ONCHAINID: ethers.constants.AddressZero, + irAgents: [], + tokenAgents: [], + complianceModules: [], + complianceSettings: [], + }, + { + claimTopics: [], + issuers: [], + issuerClaims: [], + }, + ), + ).to.be.revertedWithCustomError(gateway, 'TREXGatewayIsPaused'); + }); + }); describe('when called by not deployer', () => { describe('when public deployments disabled', () => { it('should revert', async () => { @@ -1030,6 +1061,41 @@ describe('TREXGateway', () => { }); }); describe('.batchDeployTREXSuite()', () => { + describe('when contract is paused', () => { + it('should revert', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [context.factories.trexFactory.address, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + await gateway.pause(); + + const tokenDetailsArray = []; + const claimDetailsArray = []; + for (let i = 0; i < 5; i += 1) { + tokenDetailsArray.push({ + owner: context.accounts.anotherWallet.address, + name: `Token name ${i}`, + symbol: `SYM${i}`, + decimals: 8, + irs: ethers.constants.AddressZero, + ONCHAINID: ethers.constants.AddressZero, + irAgents: [], + tokenAgents: [], + complianceModules: [], + complianceSettings: [], + }); + claimDetailsArray.push({ + claimTopics: [], + issuers: [], + issuerClaims: [], + }); + } + + await expect( + gateway.connect(context.accounts.anotherWallet).batchDeployTREXSuite(tokenDetailsArray, claimDetailsArray), + ).to.be.revertedWithCustomError(gateway, 'TREXGatewayIsPaused'); + }); + }); describe('when called by not deployer', () => { describe('when public deployments disabled', () => { it('should revert for batch deployment', async () => { @@ -1361,4 +1427,128 @@ describe('TREXGateway', () => { }); }); }); + describe('.pause()', () => { + describe('when called by not admin', () => { + it('should revert', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [context.factories.trexFactory.address, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + + await expect(gateway.connect(context.accounts.anotherWallet).pause()).to.be.revertedWithCustomError(gateway, 'OnlyAdminCall'); + }); + }); + describe('when called by owner', () => { + describe('if contract is already paused', () => { + it('should revert', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [ethers.constants.AddressZero, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + + await gateway.pause(); + await expect(gateway.pause()).to.be.revertedWithCustomError(gateway, 'TREXGatewayIsPaused'); + }); + }); + describe('if contract is not paused', () => { + it('should pause', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [ethers.constants.AddressZero, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + + const tx = await gateway.pause(); + expect(tx).to.emit(gateway, 'GatewayPaused').withArgs(context.accounts.deployer.address); + }); + }); + }); + describe('when called by agent', () => { + describe('if contract is already paused', () => { + it('should revert', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [ethers.constants.AddressZero, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + + await gateway.addAgent(context.accounts.tokenAgent.address); + await gateway.pause(); + await expect(gateway.connect(context.accounts.tokenAgent).pause()).to.be.revertedWithCustomError(gateway, 'TREXGatewayIsPaused'); + }); + }); + describe('if contract is not paused', () => { + it('should pause', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [ethers.constants.AddressZero, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + + await gateway.addAgent(context.accounts.tokenAgent.address); + const tx = await gateway.connect(context.accounts.tokenAgent).pause(); + expect(tx).to.emit(gateway, 'GatewayPaused').withArgs(context.accounts.tokenAgent.address); + }); + }); + }); + }); + describe('.unpause()', () => { + describe('when called by not admin', () => { + it('should revert', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [context.factories.trexFactory.address, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + await gateway.pause(); + + await expect(gateway.connect(context.accounts.anotherWallet).unpause()).to.be.revertedWithCustomError(gateway, 'OnlyAdminCall'); + }); + }); + describe('when called by owner', () => { + describe('if contract is not paused', () => { + it('should revert', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [ethers.constants.AddressZero, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + await expect(gateway.unpause()).to.be.revertedWithCustomError(gateway, 'TREXGatewayIsNotPaused'); + }); + }); + describe('if contract is paused', () => { + it('should unpause', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [ethers.constants.AddressZero, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + await gateway.pause(); + + const tx = await gateway.unpause(); + expect(tx).to.emit(gateway, 'GatewayUnpaused').withArgs(context.accounts.deployer.address); + }); + }); + }); + describe('when called by agent', () => { + describe('if contract is not paused', () => { + it('should revert', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [ethers.constants.AddressZero, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + + await gateway.addAgent(context.accounts.tokenAgent.address); + await expect(gateway.connect(context.accounts.tokenAgent).unpause()).to.be.revertedWithCustomError(gateway, 'TREXGatewayIsNotPaused'); + }); + }); + describe('if contract is paused', () => { + it('should unpause', async () => { + const context = await loadFixture(deployFullSuiteFixture); + + const gateway = await ethers.deployContract('TREXGateway', [ethers.constants.AddressZero, false], context.accounts.deployer); + await context.factories.trexFactory.transferOwnership(gateway.address); + await gateway.pause(); + + await gateway.addAgent(context.accounts.tokenAgent.address); + const tx = await gateway.connect(context.accounts.tokenAgent).unpause(); + expect(tx).to.emit(gateway, 'GatewayUnpaused').withArgs(context.accounts.tokenAgent.address); + }); + }); + }); + }); });