diff --git a/contracts/ScopeGuard.sol b/contracts/ScopeGuard.sol index c22a776..e74bdc5 100644 --- a/contracts/ScopeGuard.sol +++ b/contracts/ScopeGuard.sol @@ -4,19 +4,48 @@ pragma solidity ^0.8.6; import "@gnosis.pm/zodiac/contracts/guard/BaseGuard.sol"; import "@gnosis.pm/zodiac/contracts/factory/FactoryFriendly.sol"; +enum Clearance { + None, + Target, + Function +} + +enum ExecutionOptions { + None, + Send, + DelegateCall, + Both +} + contract ScopeGuard is FactoryFriendly, BaseGuard { - event SetTargetAllowed(address target, bool allowed); - event SetTargetScoped(address target, bool scoped); - event SetFallbackAllowedOnTarget(address target, bool allowed); - event SetValueAllowedOnTarget(address target, bool allowed); - event SetDelegateCallAllowedOnTarget(address target, bool allowed); - event SetFunctionAllowedOnTarget( - address target, - bytes4 functionSig, - bool allowed - ); + event AllowTarget(address target); + event RevokeTarget(address target); + event ScopeTarget(address target); + + event SetExecutionOptions(address target, ExecutionOptions options); + + event ScopeAllowFunction(address target, bytes4 functionSig); + event ScopeRevokeFunction(address target, bytes4 functionSig); + event ScopeGuardSetup(address indexed initiator, address indexed owner); + /// Function signature too short + error FunctionSignatureTooShort(); + + /// Role not allowed to send to target address + error SendNotAllowed(); + + /// Role not allowed to delegate call to target address + error DelegateCallNotAllowed(); + + /// Role not allowed to call target address + error TargetAddressNotAllowed(); + + /// Role not allowed to call this function on target address + error FunctionNotAllowed(); + + bytes4 internal constant FALLBACK_FUNCTION_SIG = 0x00000000; + constructor(address _owner) { bytes memory initializeParams = abi.encode(_owner); setUp(initializeParams); @@ -34,140 +63,134 @@ contract ScopeGuard is FactoryFriendly, BaseGuard { } struct Target { - bool allowed; - bool scoped; - bool delegateCallAllowed; - bool fallbackAllowed; - bool valueAllowed; + Clearance clearance; + ExecutionOptions options; mapping(bytes4 => bool) allowedFunctions; } - mapping(address => Target) public allowedTargets; + mapping(address => Target) public targets; - /// @dev Set whether or not calls can be made to an address. + /// @dev Allows all calls made to an address. /// @notice Only callable by owner. - /// @param target Address to be allowed/disallowed. - /// @param allow Bool to allow (true) or disallow (false) calls to target. - function setTargetAllowed(address target, bool allow) public onlyOwner { - allowedTargets[target].allowed = allow; - emit SetTargetAllowed(target, allowedTargets[target].allowed); + /// @param target Address to be allowed + function allowTarget(address target) public onlyOwner { + targets[target].clearance = Clearance.Target; + emit AllowTarget(target); } - /// @dev Set whether or not delegate calls can be made to a target. + /// @dev Disallows all calls made to an address. /// @notice Only callable by owner. - /// @param target Address to which delegate calls should be allowed/disallowed. - /// @param allow Bool to allow (true) or disallow (false) delegate calls to target. - function setDelegateCallAllowedOnTarget(address target, bool allow) - public - onlyOwner - { - allowedTargets[target].delegateCallAllowed = allow; - emit SetDelegateCallAllowedOnTarget( - target, - allowedTargets[target].delegateCallAllowed - ); + /// @param target Address to be disallowed. + function revokeTarget(address target) public onlyOwner { + targets[target].clearance = Clearance.None; + emit RevokeTarget(target); } - /// @dev Sets whether or not calls to an address should be scoped to specific function signatures. + /// @dev Scopes calls to an address, limited to specific function signatures. /// @notice Only callable by owner. - /// @param target Address to be scoped/unscoped. - /// @param scoped Bool to scope (true) or unscope (false) function calls on target. - function setScoped(address target, bool scoped) public onlyOwner { - allowedTargets[target].scoped = scoped; - emit SetTargetScoped(target, allowedTargets[target].scoped); + /// @param target Address to be scoped. + function scopeTarget(address target) public onlyOwner { + targets[target].clearance = Clearance.Function; + emit ScopeTarget(target); } - /// @dev Sets whether or not a target can be sent to (incluces fallback/receive functions). + /// @dev Sets whether or not delegate calls and/or eth can be sent to a target. /// @notice Only callable by owner. - /// @param target Address to be allow/disallow sends to. - /// @param allow Bool to allow (true) or disallow (false) sends on target. - function setFallbackAllowedOnTarget(address target, bool allow) + /// @param target Address to which delegate calls should be allowed/disallowed. + /// @param options One of None, Send, DelegateCall or Both + function setExecutionOptions(address target, ExecutionOptions options) public onlyOwner { - allowedTargets[target].fallbackAllowed = allow; - emit SetFallbackAllowedOnTarget( - target, - allowedTargets[target].fallbackAllowed - ); + targets[target].options = options; + emit SetExecutionOptions(target, options); } - /// @dev Sets whether or not a target can be sent to (incluces fallback/receive functions). + /// @dev Allows a specific function signature on a scoped target. /// @notice Only callable by owner. - /// @param target Address to be allow/disallow sends to. - /// @param allow Bool to allow (true) or disallow (false) sends on target. - function setValueAllowedOnTarget(address target, bool allow) + /// @param target Scoped address on which a function signature should be allowed + /// @param functionSig Function signature to be allowed + function scopeAllowFunction(address target, bytes4 functionSig) public onlyOwner { - allowedTargets[target].valueAllowed = allow; - emit SetValueAllowedOnTarget( - target, - allowedTargets[target].valueAllowed - ); + targets[target].allowedFunctions[functionSig] = true; + emit ScopeAllowFunction(target, functionSig); } - /// @dev Sets whether or not a specific function signature should be allowed on a scoped target. + /// @dev Disallows a specific function signature on a scoped target. /// @notice Only callable by owner. - /// @param target Scoped address on which a function signature should be allowed/disallowed. - /// @param functionSig Function signature to be allowed/disallowed. - /// @param allow Bool to allow (true) or disallow (false) calls a function signature on target. - function setAllowedFunction( - address target, - bytes4 functionSig, - bool allow - ) public onlyOwner { - allowedTargets[target].allowedFunctions[functionSig] = allow; - emit SetFunctionAllowedOnTarget( - target, - functionSig, - allowedTargets[target].allowedFunctions[functionSig] - ); + /// @param target Scoped address on which a function signature should be disallowed + /// @param functionSig Function signature to be disallowed + function scopeRevokeFunction(address target, bytes4 functionSig) + public + onlyOwner + { + targets[target].allowedFunctions[functionSig] = false; + emit ScopeRevokeFunction(target, functionSig); } - /// @dev Returns bool to indicate if an address is an allowed target. - /// @param target Address to check. - function isAllowedTarget(address target) public view returns (bool) { - return (allowedTargets[target].allowed); + /// @dev Allows the fallback function on a scoped target. + /// @notice Only callable by owner. + /// @param target Scoped address on which a function signature should be allowed + function scopeAllowFallback(address target) public onlyOwner { + scopeAllowFunction(target, FALLBACK_FUNCTION_SIG); } - /// @dev Returns bool to indicate if an address is scoped. - /// @param target Address to check. - function isScoped(address target) public view returns (bool) { - return (allowedTargets[target].scoped); + /// @dev Disallows the fallback function on a scoped target. + /// @notice Only callable by owner. + /// @param target Scoped address on which a function signature should be disallowed + function scopeRevokeFallback(address target) public onlyOwner { + scopeRevokeFunction(target, FALLBACK_FUNCTION_SIG); } - /// @dev Returns bool to indicate if fallback is allowed to a target. + /// @dev Returns bool to indicate if an address is an allowed target. /// @param target Address to check. - function isfallbackAllowed(address target) public view returns (bool) { - return (allowedTargets[target].fallbackAllowed); + function isTargetAllowed(address target) public view returns (bool) { + return targets[target].clearance == Clearance.Target; } - /// @dev Returns bool to indicate if ETH can be sent to a target. + /// @dev Returns bool to indicate if an address is scoped. /// @param target Address to check. - function isValueAllowed(address target) public view returns (bool) { - return (allowedTargets[target].valueAllowed); + function isTargetScoped(address target) public view returns (bool) { + return targets[target].clearance == Clearance.Function; } /// @dev Returns bool to indicate if a function signature is allowed for a target address. /// @param target Address to check. /// @param functionSig Signature to check. - function isAllowedFunction(address target, bytes4 functionSig) + function isFunctionAllowed(address target, bytes4 functionSig) public view returns (bool) { - return (allowedTargets[target].allowedFunctions[functionSig]); + return targets[target].allowedFunctions[functionSig] == true; + } + + /// @dev Returns bool to indicate if fallback is allowed on a target. + /// @param target Address to check. + function isFallbackAllowed(address target) public view returns (bool) { + return targets[target].allowedFunctions[FALLBACK_FUNCTION_SIG] == true; + } + + /// @dev Returns bool to indicate if ETH can be sent to a target. + /// @param target Address to check. + function canSendToTarget(address target) public view returns (bool) { + return + targets[target].options == ExecutionOptions.Send || + targets[target].options == ExecutionOptions.Both; } /// @dev Returns bool to indicate if delegate calls are allowed to a target address. /// @param target Address to check. - function isAllowedToDelegateCall(address target) + function canDelegateCallToTarget(address target) public view returns (bool) { - return (allowedTargets[target].delegateCallAllowed); + return + targets[target].options == ExecutionOptions.DelegateCall || + targets[target].options == ExecutionOptions.Both; } // solhint-disallow-next-line payable-fallback @@ -190,32 +213,46 @@ contract ScopeGuard is FactoryFriendly, BaseGuard { bytes memory, address ) external view override { - require( - operation != Enum.Operation.DelegateCall || - allowedTargets[to].delegateCallAllowed, - "Delegate call not allowed to this address" - ); - require(allowedTargets[to].allowed, "Target address is not allowed"); - if (value > 0) { - require( - allowedTargets[to].valueAllowed, - "Cannot send ETH to this target" - ); + if (data.length != 0 && data.length < 4) { + revert FunctionSignatureTooShort(); + } + + Target storage target = targets[to]; + + if (target.clearance == Clearance.None) { + revert TargetAddressNotAllowed(); + } + + if ( + value > 0 && + target.options != ExecutionOptions.Send && + target.options != ExecutionOptions.Both + ) { + // isSend && !canSend + revert SendNotAllowed(); } - if (data.length >= 4) { - require( - !allowedTargets[to].scoped || - allowedTargets[to].allowedFunctions[bytes4(data)], - "Target function is not allowed" - ); - } else { - require(data.length == 0, "Function signature too short"); - require( - !allowedTargets[to].scoped || - allowedTargets[to].fallbackAllowed, - "Fallback not allowed for this address" - ); + + if ( + operation == Enum.Operation.DelegateCall && + target.options != ExecutionOptions.DelegateCall && + target.options != ExecutionOptions.Both + ) { + // isDelegateCall && !canDelegateCall + revert DelegateCallNotAllowed(); + } + + if (target.clearance == Clearance.Target) { + return; } + + if (target.clearance == Clearance.Function) { + if (targets[to].allowedFunctions[bytes4(data)] == false) { + revert FunctionNotAllowed(); + } + return; + } + + assert(false); } function checkAfterExecution(bytes32, bool) external view override {} diff --git a/test/ScopeGuard.spec.ts b/test/ScopeGuard.spec.ts index a66f0e1..34d6b9b 100644 --- a/test/ScopeGuard.spec.ts +++ b/test/ScopeGuard.spec.ts @@ -1,32 +1,39 @@ -import { expect } from "chai"; -import hre, { deployments, waffle, ethers } from "hardhat"; -import "@nomiclabs/hardhat-ethers"; -import { AddressZero } from "@ethersproject/constants"; - -describe("ScopeGuard", async () => { +import { expect } from 'chai'; +import hre, { deployments, waffle, ethers } from 'hardhat'; +import '@nomiclabs/hardhat-ethers'; +import { AddressZero } from '@ethersproject/constants'; + +enum ExecutionOptions { + NONE = 0, + SEND = 1, + DELEGATE_CALL = 2, + BOTH = 3, +} + +describe('ScopeGuard', async () => { const [user1, user2] = waffle.provider.getWallets(); const abiCoder = new ethers.utils.AbiCoder(); - const initializeParams = abiCoder.encode(["address"], [user1.address]); + const initializeParams = abiCoder.encode(['address'], [user1.address]); const setupTests = deployments.createFixture(async ({ deployments }) => { await deployments.fixture(); - const avatarFactory = await hre.ethers.getContractFactory("TestAvatar"); + const avatarFactory = await hre.ethers.getContractFactory('TestAvatar'); const avatar = await avatarFactory.deploy(); - const guardFactory = await hre.ethers.getContractFactory("ScopeGuard"); + const guardFactory = await hre.ethers.getContractFactory('ScopeGuard'); const guard = await guardFactory.deploy(user1.address); await avatar.enableModule(user1.address); await avatar.setGuard(guard.address); const tx = { to: avatar.address, value: 0, - data: "0x", + data: '0x', operation: 0, avatarTxGas: 0, baseGas: 0, gasPrice: 0, gasToken: AddressZero, refundReceiver: AddressZero, - signatures: "0x", + signatures: '0x', }; return { avatar, @@ -35,54 +42,54 @@ describe("ScopeGuard", async () => { }; }); - describe("setUp()", async () => { - it("throws if guard has already been initialized", async () => { + describe('setUp()', async () => { + it('throws if guard has already been initialized', async () => { const { guard } = await setupTests(); await expect(guard.setUp(initializeParams)).to.be.revertedWith( - "Initializable: contract is already initialized" + 'Initializable: contract is already initialized' ); }); - it("throws if owner is zero address", async () => { - const Guard = await hre.ethers.getContractFactory("ScopeGuard"); + it('throws if owner is zero address', async () => { + const Guard = await hre.ethers.getContractFactory('ScopeGuard'); await expect(Guard.deploy(AddressZero)).to.be.revertedWith( - "Ownable: new owner is the zero address" + 'Ownable: new owner is the zero address' ); }); - it("should emit event because of successful set up", async () => { - const Guard = await hre.ethers.getContractFactory("ScopeGuard"); + it('should emit event because of successful set up', async () => { + const Guard = await hre.ethers.getContractFactory('ScopeGuard'); const guard = await Guard.deploy(user1.address); await guard.deployed(); await expect(guard.deployTransaction) - .to.emit(guard, "ScopeGuardSetup") + .to.emit(guard, 'ScopeGuardSetup') .withArgs(user1.address, user1.address); }); }); - describe("fallback", async () => { - it("must NOT revert on fallback without value", async () => { + describe('fallback', async () => { + it('must NOT revert on fallback without value', async () => { const { guard } = await setupTests(); await user1.sendTransaction({ to: guard.address, - data: "0xbaddad", + data: '0xbaddad', }); }); - it("should revert on fallback with value", async () => { + it('should revert on fallback with value', async () => { const { guard } = await setupTests(); await expect( user1.sendTransaction({ to: guard.address, - data: "0xbaddad", + data: '0xbaddad', value: 1, }) ).to.be.reverted; }); }); - describe("checkTransaction()", async () => { - it("should revert if target is not allowed", async () => { + describe('checkTransaction()', async () => { + it('should revert if target is not allowed', async () => { const { guard, tx } = await setupTests(); await expect( guard.checkTransaction( @@ -98,12 +105,13 @@ describe("ScopeGuard", async () => { tx.signatures, user1.address ) - ).to.be.revertedWith("Target address is not allowed"); + ).to.be.revertedWith('TargetAddressNotAllowed()'); }); - it("should revert delegate call if delegate calls are not allowed to target", async () => { + it('should revert delegate call if delegate calls are not allowed to target', async () => { const { guard, tx } = await setupTests(); tx.operation = 1; + await guard.allowTarget(tx.to); await expect( guard.checkTransaction( tx.to, @@ -118,17 +126,17 @@ describe("ScopeGuard", async () => { tx.signatures, user1.address ) - ).to.be.revertedWith("Delegate call not allowed to this address"); + ).to.be.revertedWith('DelegateCallNotAllowed()'); }); - it("should allow delegate call if delegate calls are allowed to target", async () => { + it('should allow delegate call if delegate calls are allowed to target', async () => { const { guard, avatar, tx } = await setupTests(); - await guard.setTargetAllowed(avatar.address, true); - await guard.setDelegateCallAllowedOnTarget(avatar.address, true); + await guard.allowTarget(avatar.address); + tx.operation = 1; - await expect( + const checkTransaction = () => guard.checkTransaction( tx.to, tx.value, @@ -141,15 +149,34 @@ describe("ScopeGuard", async () => { tx.refundReceiver, tx.signatures, user1.address - ) + ); + + await guard.setExecutionOptions(avatar.address, ExecutionOptions.NONE); + await expect(checkTransaction()).to.be.revertedWith( + 'DelegateCallNotAllowed()' + ); + + await guard.setExecutionOptions(avatar.address, ExecutionOptions.SEND); + await expect(checkTransaction()).to.be.revertedWith( + 'DelegateCallNotAllowed()' + ); + + await guard.setExecutionOptions( + avatar.address, + ExecutionOptions.DELEGATE_CALL ); + await expect(checkTransaction()).to.not.be.reverted; + + await guard.setExecutionOptions(avatar.address, ExecutionOptions.BOTH); + await expect(checkTransaction()).to.not.be.reverted; }); - it("should revert if scoped and target function is not allowed", async () => { + it('should revert if scoped and target function is not allowed', async () => { const { avatar, guard, tx } = await setupTests(); - await guard.setTargetAllowed(avatar.address, true); - await guard.setScoped(avatar.address, true); - tx.data = "0x12345678"; + + await guard.scopeTarget(avatar.address); + + tx.data = '0x12345678'; tx.operation = 0; await expect( @@ -166,13 +193,12 @@ describe("ScopeGuard", async () => { tx.signatures, user1.address ) - ).to.be.revertedWith("Target function is not allowed"); + ).to.be.revertedWith('FunctionNotAllowed()'); }); - it("should revert if scoped and transaction data is greater than 0 and less than 4", async () => { + it('should revert if scoped and transaction data is greater than 0 and less than 4', async () => { const { avatar, guard, tx } = await setupTests(); - await guard.setTargetAllowed(avatar.address, true); - await guard.setScoped(avatar.address, true); + await guard.scopeTarget(avatar.address); await expect( guard.checkTransaction( tx.to, @@ -187,14 +213,13 @@ describe("ScopeGuard", async () => { tx.signatures, user1.address ) - ).to.be.revertedWith("Function signature too short"); + ).to.be.revertedWith('FunctionSignatureTooShort()'); }); - it("should revert if scoped and empty transaction data is disallowed", async () => { + it('should revert if scoped and empty transaction data is disallowed', async () => { const { avatar, guard, tx } = await setupTests(); - await guard.setTargetAllowed(avatar.address, true); - await guard.setScoped(avatar.address, true); - tx.data = "0x"; + await guard.scopeTarget(avatar.address); + tx.data = '0x'; await expect( guard.checkTransaction( tx.to, @@ -209,17 +234,15 @@ describe("ScopeGuard", async () => { tx.signatures, user1.address ) - ).to.be.revertedWith("Fallback not allowed for this address"); + //).to.be.revertedWith('Fallback not allowed for this address'); + ).to.be.revertedWith('FunctionNotAllowed()'); }); - it("should revert if function sig is 0x00000000 and not explicitly approved", async () => { + it('should revert if function sig is 0x00000000 and not explicitly approved', async () => { const { avatar, guard, tx } = await setupTests(); - await guard.setTargetAllowed(avatar.address, true); - await guard.setScoped(avatar.address, true); - await guard.setFallbackAllowedOnTarget(avatar.address, false); - await guard.setAllowedFunction(avatar.address, "0x00000000", false); - tx.data = "0x00000000"; - await expect( + await guard.scopeTarget(avatar.address); + tx.data = '0x00000000'; + const checkTransaction = () => guard.checkTransaction( tx.to, tx.value, @@ -232,14 +255,27 @@ describe("ScopeGuard", async () => { tx.refundReceiver, tx.signatures, user1.address - ) - ).to.be.revertedWith("Target function is not allowed"); + ); + + await expect(checkTransaction()).to.be.revertedWith('FunctionNotAllowed'); + + await guard.scopeAllowFallback(avatar.address); + await expect(checkTransaction()).to.not.be.reverted; + + await guard.scopeRevokeFallback(avatar.address); + await expect(checkTransaction()).to.be.revertedWith('FunctionNotAllowed'); + + await guard.scopeAllowFunction(avatar.address, '0x00000000'); + await expect(checkTransaction()).to.not.be.reverted; + + await guard.scopeRevokeFunction(avatar.address, '0x00000000'); + await expect(checkTransaction()).to.be.revertedWith('FunctionNotAllowed'); }); - it("should revert if value is greater than zero and value is not allowed", async () => { + it('should revert if value is greater than zero and value is not allowed', async () => { const { avatar, guard, tx } = await setupTests(); - await guard.setTargetAllowed(avatar.address, true); - tx.data = "0x12345678"; + await guard.allowTarget(avatar.address); + tx.data = '0x12345678'; tx.value = 1; await expect( guard.checkTransaction( @@ -255,17 +291,17 @@ describe("ScopeGuard", async () => { tx.signatures, user1.address ) - ).to.be.revertedWith("Cannot send ETH to this target"); + ).to.be.revertedWith('SendNotAllowed()'); }); - it("should send ETH to target if value is allowed", async () => { + it('should send ETH to target if value is allowed', async () => { const { avatar, guard, tx } = await setupTests(); - await guard.setTargetAllowed(avatar.address, true); - await guard.setValueAllowedOnTarget(avatar.address, true); - tx.data = "0x12345678"; + await guard.allowTarget(avatar.address); + + tx.data = '0x12345678'; tx.value = 1; - expect( - await guard.checkTransaction( + const checkTransaction = () => + guard.checkTransaction( tx.to, tx.value, tx.data, @@ -277,13 +313,27 @@ describe("ScopeGuard", async () => { tx.refundReceiver, tx.signatures, user1.address - ) + ); + + await guard.setExecutionOptions(avatar.address, ExecutionOptions.NONE); + await expect(checkTransaction()).to.be.revertedWith('SendNotAllowed()'); + + await guard.setExecutionOptions(avatar.address, ExecutionOptions.SEND); + await expect(checkTransaction()).to.not.be.reverted; + + await guard.setExecutionOptions( + avatar.address, + ExecutionOptions.DELEGATE_CALL ); + await expect(checkTransaction()).to.be.revertedWith('SendNotAllowed()'); + + await guard.setExecutionOptions(avatar.address, ExecutionOptions.BOTH); + await expect(checkTransaction()).to.not.be.reverted; }); - it("should be callable by an avatar", async () => { + it('should be callable by an avatar', async () => { const { avatar, guard, tx } = await setupTests(); - expect(guard.setTargetAllowed(guard.address, true)); + await guard.allowTarget(guard.address); tx.operation = 0; tx.to = guard.address; tx.value = 0; @@ -300,324 +350,415 @@ describe("ScopeGuard", async () => { tx.refundReceiver, tx.signatures ) - ); + ).to.not.be.reverted; }); }); - describe("setTargetAllowed()", async () => { - it("should revert if caller is not owner", async () => { + describe('allowTarget()', async () => { + it('should revert if caller is not owner', async () => { const { guard } = await setupTests(); - expect( - guard.connect(user2).setTargetAllowed(guard.address, true) - ).to.be.revertedWith("caller is not the owner"); + await expect( + guard.connect(user2).allowTarget(guard.address) + ).to.be.revertedWith('caller is not the owner'); }); - it("should allow a target", async () => { - const { avatar, guard } = await setupTests(); - expect(await guard.isAllowedTarget(guard.address)).to.be.equals(false); - expect(guard.setTargetAllowed(guard.address, true)) - .to.emit(guard, "SetTargetAllowed") - .withArgs(guard.address, true); - await expect(await guard.isAllowedTarget(guard.address)).to.be.equals( - true - ); + it('should allow a target', async () => { + const { guard } = await setupTests(); + expect(await guard.isTargetAllowed(guard.address)).to.be.equals(false); + await expect(guard.allowTarget(guard.address)) + .to.emit(guard, 'AllowTarget') + .withArgs(guard.address); + expect(await guard.isTargetAllowed(guard.address)).to.be.equals(true); }); - it("should disallow a target", async () => { + it('should emit AllowTarget(target)', async () => { const { avatar, guard } = await setupTests(); - expect(await guard.isAllowedTarget(guard.address)).to.be.equals(false); - expect(guard.setTargetAllowed(guard.address, true)) - .to.emit(guard, "SetTargetAllowed") - .withArgs(guard.address, true); - expect(guard.setTargetAllowed(guard.address, false)) - .to.emit(guard, "SetTargetAllowed") - .withArgs(guard.address, false); - expect(await guard.isAllowedTarget(guard.address)).to.be.equals(false); + await expect(guard.allowTarget(guard.address)) + .to.emit(guard, 'AllowTarget') + .withArgs(guard.address); }); + }); - it("should emit SetTargetAllowed(target, allowed)", async () => { - const { avatar, guard } = await setupTests(); - expect(guard.setTargetAllowed(guard.address, false)) - .to.emit(guard, "SetTargetAllowed") - .withArgs(guard.address, false); + describe('revokeTarget()', async () => { + it('should revert if caller is not owner', async () => { + const { guard } = await setupTests(); + await expect( + guard.connect(user2).revokeTarget(guard.address) + ).to.be.revertedWith('caller is not the owner'); + }); + + it('should disallow a target', async () => { + const { guard } = await setupTests(); + await guard.allowTarget(guard.address); + + expect(await guard.isTargetAllowed(guard.address)).to.be.equals(true); + await expect(guard.revokeTarget(guard.address)) + .to.emit(guard, 'RevokeTarget') + .withArgs(guard.address); + expect(await guard.isTargetAllowed(guard.address)).to.be.equals(false); + }); + + it('should emit RevokeTarget(target)', async () => { + const { guard } = await setupTests(); + expect(guard.revokeTarget(guard.address)) + .to.emit(guard, 'RevokeTarget') + .withArgs(guard.address); }); }); - describe("setDelegateCallAllowedOnTarget()", async () => { - it("should revert if caller is not owner", async () => { + describe('scopeTarget()', async () => { + it('should revert if caller is not owner', async () => { const { guard } = await setupTests(); expect( - guard.connect(user2).setDelegateCallAllowedOnTarget(guard.address, true) - ).to.be.revertedWith("caller is not the owner"); + guard.connect(user2).scopeTarget(guard.address) + ).to.be.revertedWith('caller is not the owner'); }); - it("should allow delegate calls for a target", async () => { - const { avatar, guard } = await setupTests(); - expect(await guard.isAllowedToDelegateCall(guard.address)).to.be.equals( + it('should set clearance Function for a target', async () => { + const { guard } = await setupTests(); + + expect(await guard.isTargetScoped(guard.address)).to.be.equals(false); + await expect(guard.scopeTarget(guard.address)) + .to.emit(guard, 'ScopeTarget') + .withArgs(guard.address); + expect(await guard.isTargetScoped(guard.address)).to.be.equals(true); + }); + + it('should set scoped to false for a target', async () => { + const { guard } = await setupTests(); + + expect(await guard.isTargetScoped(guard.address)).to.be.equals(false); + await expect(guard.scopeTarget(guard.address)) + .to.emit(guard, 'ScopeTarget') + .withArgs(guard.address); + + await guard.revokeTarget(guard.address); + expect(await guard.isTargetScoped(guard.address)).to.be.equals(false); + }); + + it('should emit ScopedTarget(target)', async () => { + const { guard } = await setupTests(); + + await expect(guard.scopeTarget(guard.address)) + .to.emit(guard, 'ScopeTarget') + .withArgs(guard.address); + }); + }); + + describe('setExecutionOptions()', async () => { + it('should revert if caller is not owner', async () => { + const { guard } = await setupTests(); + expect( + guard + .connect(user2) + .setExecutionOptions(guard.address, ExecutionOptions.BOTH) + ).to.be.revertedWith('caller is not the owner'); + }); + + it('should allow delegate calls to a target', async () => { + const { guard } = await setupTests(); + expect(await guard.canDelegateCallToTarget(guard.address)).to.be.equals( false ); - expect(guard.setDelegateCallAllowedOnTarget(guard.address, true)) - .to.emit(guard, "SetDelegateCallAllowedOnTarget") - .withArgs(guard.address, true); - expect(await guard.isAllowedToDelegateCall(guard.address)).to.be.equals( + await expect( + guard.setExecutionOptions(guard.address, ExecutionOptions.DELEGATE_CALL) + ) + .to.emit(guard, 'SetExecutionOptions') + .withArgs(guard.address, ExecutionOptions.DELEGATE_CALL); + expect(await guard.canDelegateCallToTarget(guard.address)).to.be.equals( true ); }); - it("should disallow delegate calls for a target", async () => { - const { avatar, guard } = await setupTests(); - expect(await guard.isAllowedToDelegateCall(guard.address)).to.be.equals( + it('should disallow delegate calls to a target', async () => { + const { guard } = await setupTests(); + expect(await guard.canDelegateCallToTarget(guard.address)).to.be.equals( false ); - expect(guard.setDelegateCallAllowedOnTarget(guard.address, true)) - .to.emit(guard, "SetDelegateCallAllowedOnTarget") - .withArgs(guard.address, true); - expect(guard.setDelegateCallAllowedOnTarget(guard.address, false)) - .to.emit(guard, "SetDelegateCallAllowedOnTarget") - .withArgs(guard.address, false); - expect(await guard.isAllowedToDelegateCall(guard.address)).to.be.equals( + await expect( + guard.setExecutionOptions(guard.address, ExecutionOptions.BOTH) + ) + .to.emit(guard, 'SetExecutionOptions') + .withArgs(guard.address, ExecutionOptions.BOTH); + await expect( + guard.setExecutionOptions(guard.address, ExecutionOptions.NONE) + ) + .to.emit(guard, 'SetExecutionOptions') + .withArgs(guard.address, ExecutionOptions.NONE); + expect(await guard.canDelegateCallToTarget(guard.address)).to.be.equals( false ); }); - it("should emit DelegateCallsAllowedOnTarget(target, allowed)", async () => { + it('should return true if target is allowed to delegate call', async () => { const { avatar, guard } = await setupTests(); - expect(guard.setDelegateCallAllowedOnTarget(guard.address, true)) - .to.emit(guard, "SetDelegateCallAllowedOnTarget") - .withArgs(guard.address, true); + + expect(await guard.canDelegateCallToTarget(avatar.address)).to.be.equals( + false + ); + await expect(guard.setExecutionOptions(avatar.address, 2)); + + expect(await guard.canDelegateCallToTarget(avatar.address)).to.be.equals( + true + ); }); - }); - describe("setScoped()", async () => { - it("should revert if caller is not owner", async () => { + it('should allow sending to a target', async () => { const { guard } = await setupTests(); - expect( - guard.connect(user2).setScoped(guard.address, true) - ).to.be.revertedWith("caller is not the owner"); + expect(await guard.canSendToTarget(guard.address)).to.be.equals(false); + await expect( + guard.setExecutionOptions(guard.address, ExecutionOptions.SEND) + ) + .to.emit(guard, 'SetExecutionOptions') + .withArgs(guard.address, ExecutionOptions.SEND); + expect(await guard.canSendToTarget(guard.address)).to.be.equals(true); }); - it("should set scoped to true for a target", async () => { + it('should disallow sending to a target', async () => { const { guard } = await setupTests(); + expect(await guard.canSendToTarget(guard.address)).to.be.equals(false); + await expect( + guard.setExecutionOptions(guard.address, ExecutionOptions.SEND) + ); + expect(await guard.canSendToTarget(guard.address)).to.be.equals(true); - expect(await guard.isScoped(guard.address)).to.be.equals(false); - expect(guard.setScoped(guard.address, true)) - .to.emit(guard, "SetTargetScoped") - .withArgs(guard.address, true); - expect(await guard.isScoped(guard.address)).to.be.equals(true); + await expect( + guard.setExecutionOptions(guard.address, ExecutionOptions.NONE) + ); + expect(await guard.canSendToTarget(guard.address)).to.be.equals(false); }); - it("should set scoped to false for a target", async () => { - const { guard } = await setupTests(); + it('should return true if allowed to send to a target', async () => { + const { avatar, guard } = await setupTests(); - expect(await guard.isScoped(guard.address)).to.be.equals(false); - expect(guard.setScoped(guard.address, true)) - .to.emit(guard, "SetTargetScoped") - .withArgs(guard.address, true); - expect(guard.setScoped(guard.address, false)) - .to.emit(guard, "SetTargetScoped") - .withArgs(guard.address, false); - expect(await guard.isScoped(guard.address)).to.be.equals(false); + expect(await guard.canSendToTarget(avatar.address)).to.be.equals(false); + await expect( + guard.setExecutionOptions(avatar.address, ExecutionOptions.SEND) + ); + expect(await guard.canSendToTarget(avatar.address)).to.be.equals(true); + await expect( + guard.setExecutionOptions(avatar.address, ExecutionOptions.BOTH) + ); + expect(await guard.canSendToTarget(avatar.address)).to.be.equals(true); }); - it("should emit SetTargetScoped(target, scoped)", async () => { - const { guard } = await setupTests(); - - expect(guard.setScoped(guard.address, true)) - .to.emit(guard, "SetTargetScoped") - .withArgs(guard.address, true); + it('should emit SetExecutionOptions(target, options)', async () => { + const { avatar, guard } = await setupTests(); + await expect( + guard.setExecutionOptions(guard.address, ExecutionOptions.BOTH) + ) + .to.emit(guard, 'SetExecutionOptions') + .withArgs(guard.address, ExecutionOptions.BOTH); }); }); - describe("setFallbackAllowedOnTarget()", async () => { - it("should revert if caller is not owner", async () => { + describe('scopeAllowFallback()', async () => { + it('should revert if caller is not owner', async () => { const { guard } = await setupTests(); expect( - guard.connect(user2).setFallbackAllowedOnTarget(guard.address, true) - ).to.be.revertedWith("caller is not the owner"); + guard.connect(user2).scopeAllowFallback(guard.address) + ).to.be.revertedWith('caller is not the owner'); }); - it("should set fallbackAllowed to true for a target", async () => { + it('should set fallbackAllowed to true for a target', async () => { const { guard } = await setupTests(); - expect(await guard.isfallbackAllowed(guard.address)).to.be.equals(false); - await expect(guard.setFallbackAllowedOnTarget(guard.address, true)) - .to.emit(guard, "SetFallbackAllowedOnTarget") - .withArgs(guard.address, true); - expect(await guard.isfallbackAllowed(guard.address)).to.be.equals(true); + expect(await guard.isFallbackAllowed(guard.address)).to.be.equals(false); + await expect(guard.scopeAllowFallback(guard.address)) + .to.emit(guard, 'ScopeAllowFunction') + .withArgs(guard.address, '0x00000000'); + expect(await guard.isFallbackAllowed(guard.address)).to.be.equals(true); + }); + + it('should emit ScopeAllowFunction(target, functionSig)', async () => { + const { guard } = await setupTests(); + + await expect(guard.scopeAllowFallback(guard.address)) + .to.emit(guard, 'ScopeAllowFunction') + .withArgs(guard.address, '0x00000000'); + }); + }); + + describe('scopeRevokeFallback()', async () => { + it('should revert if caller is not owner', async () => { + const { guard } = await setupTests(); + expect( + guard.connect(user2).scopeRevokeFallback(guard.address) + ).to.be.revertedWith('caller is not the owner'); }); - it("should set fallbackAllowed to false for a target", async () => { + it('should set fallbackAllowed to false for a target', async () => { const { guard } = await setupTests(); - expect(await guard.isfallbackAllowed(guard.address)).to.be.equals(false); - await expect(guard.setFallbackAllowedOnTarget(guard.address, true)) - .to.emit(guard, "SetFallbackAllowedOnTarget") - .withArgs(guard.address, true); - await expect(guard.setFallbackAllowedOnTarget(guard.address, false)) - .to.emit(guard, "SetFallbackAllowedOnTarget") - .withArgs(guard.address, false); - expect(await guard.isfallbackAllowed(guard.address)).to.be.equals(false); + expect(await guard.isFallbackAllowed(guard.address)).to.be.equals(false); + await expect(guard.scopeAllowFallback(guard.address)) + .to.emit(guard, 'ScopeAllowFunction') + .withArgs(guard.address, '0x00000000'); + await expect(guard.scopeRevokeFallback(guard.address)) + .to.emit(guard, 'ScopeRevokeFunction') + .withArgs(guard.address, '0x00000000'); + expect(await guard.isFallbackAllowed(guard.address)).to.be.equals(false); }); - it("should emit setFallbackAllowedOnTarget(target, scoped)", async () => { + it('should emit ScopeRevokeFunction(target, functionSig)', async () => { const { guard } = await setupTests(); - await expect(guard.setFallbackAllowedOnTarget(guard.address, true)) - .to.emit(guard, "SetFallbackAllowedOnTarget") - .withArgs(guard.address, true); + await expect(guard.scopeRevokeFallback(guard.address)) + .to.emit(guard, 'ScopeRevokeFunction') + .withArgs(guard.address, '0x00000000'); }); }); - describe("setAllowedFunction()", async () => { - it("should revert if caller is not owner", async () => { + describe('scopeAllowFunction()', async () => { + it('should revert if caller is not owner', async () => { const { guard } = await setupTests(); expect( - guard - .connect(user2) - .setAllowedFunction(guard.address, "0x12345678", true) - ).to.be.revertedWith("caller is not the owner"); + guard.connect(user2).scopeAllowFunction(guard.address, '0x12345678') + ).to.be.revertedWith('caller is not the owner'); }); - it("should allow function for a target", async () => { + it('should allow function for a target', async () => { const { guard } = await setupTests(); expect( - await guard.isAllowedFunction(guard.address, "0x12345678") + await guard.isFunctionAllowed(guard.address, '0x12345678') ).to.be.equals(false); - expect(guard.setAllowedFunction(guard.address, "0x12345678", true)) - .to.emit(guard, "SetFunctionAllowedOnTarget") - .withArgs(guard.address, "0x12345678", true); + + await expect(guard.scopeAllowFunction(guard.address, '0x12345678')) + .to.emit(guard, 'ScopeAllowFunction') + .withArgs(guard.address, '0x12345678'); expect( - await guard.isAllowedFunction(guard.address, "0x12345678") + await guard.isFunctionAllowed(guard.address, '0x12345678') ).to.be.equals(true); }); - it("should disallow function for a target", async () => { + it('should emit ScopeAllowFunction(address, sig)', async () => { + const { guard } = await setupTests(); + await expect(guard.scopeAllowFunction(guard.address, '0x12345678')) + .to.emit(guard, 'ScopeAllowFunction') + .withArgs(guard.address, '0x12345678'); + }); + }); + + describe('scopeRevokeFunction()', async () => { + it('should revert if caller is not owner', async () => { + const { guard } = await setupTests(); + await expect( + guard.connect(user2).scopeRevokeFunction(guard.address, '0x12345678') + ).to.be.revertedWith('caller is not the owner'); + }); + + it('should disallow function for a target', async () => { const { guard } = await setupTests(); expect( - await guard.isAllowedFunction(guard.address, "0x12345678") + await guard.isFunctionAllowed(guard.address, '0x12345678') ).to.be.equals(false); - expect(guard.setAllowedFunction(guard.address, "0x12345678", true)) - .to.emit(guard, "SetFunctionAllowedOnTarget") - .withArgs(guard.address, "0x12345678", true); - expect(guard.setAllowedFunction(guard.address, "0x12345678", false)) - .to.emit(guard, "SetFunctionAllowedOnTarget") - .withArgs(guard.address, "0x12345678", false); + + await guard.scopeAllowFunction(guard.address, '0x12345678'); + + await expect(guard.scopeRevokeFunction(guard.address, '0x12345678')) + .to.emit(guard, 'ScopeRevokeFunction') + .withArgs(guard.address, '0x12345678'); expect( - await guard.isAllowedFunction(guard.address, "0x12345678") + await guard.isFunctionAllowed(guard.address, '0x12345678') ).to.be.equals(false); }); - it("should emit SetFunctionAllowedOnTarget(address, sig, allowed)", async () => { - const { avatar, guard } = await setupTests(); - expect(guard.setAllowedFunction(guard.address, "0x12345678", false)) - .to.emit(guard, "SetFunctionAllowedOnTarget") - .withArgs(guard.address, "0x12345678", false); + it('should emit ScopeRevokeFunctuion(address, sig)', async () => { + const { guard } = await setupTests(); + expect(guard.scopeRevokeFunction(guard.address, '0x12345678')) + .to.emit(guard, 'ScopeRevokeFunction') + .withArgs(guard.address, '0x12345678'); }); }); - describe("isAllowedTarget()", async () => { - it("should return false if not set", async () => { + describe('isTargetAllowed()', async () => { + it('should return false if not set', async () => { const { avatar, guard } = await setupTests(); - expect(await guard.isAllowedTarget(avatar.address)).to.be.equals(false); + expect(await guard.isTargetAllowed(avatar.address)).to.be.equals(false); }); - it("should return true if target is allowed", async () => { + it('should return true if target is allowed', async () => { const { avatar, guard } = await setupTests(); - expect(await guard.isAllowedTarget(avatar.address)).to.be.equals(false); - expect(guard.setTargetAllowed(avatar.address, true)); - expect(await guard.isAllowedTarget(avatar.address)).to.be.equals(true); + expect(await guard.isTargetAllowed(avatar.address)).to.be.equals(false); + await guard.allowTarget(avatar.address); + expect(await guard.isTargetAllowed(avatar.address)).to.be.equals(true); }); }); - describe("isScoped()", async () => { - it("should return false if not set", async () => { - const { avatar, guard } = await setupTests(); + describe('isTargetScoped()', async () => { + it('should return false if not set', async () => { + const { guard } = await setupTests(); - expect(await guard.isScoped(guard.address)).to.be.equals(false); + expect(await guard.isTargetScoped(guard.address)).to.be.equals(false); }); - it("should return false if set to false", async () => { + it('should return false if set to false', async () => { const { guard } = await setupTests(); - expect(guard.setScoped(guard.address, false)); - expect(await guard.isScoped(guard.address)).to.be.equals(false); + expect(await guard.isTargetScoped(guard.address)).to.be.equals(false); + await guard.scopeTarget(guard.address); + expect(await guard.isTargetScoped(guard.address)).to.be.equals(true); + + await guard.revokeTarget(guard.address); + expect(await guard.isTargetScoped(guard.address)).to.be.equals(false); }); - it("should return true if set to true", async () => { + it('should return true if set to true', async () => { const { guard } = await setupTests(); - expect(await guard.isScoped(guard.address)).to.be.equals(false); - expect(guard.setScoped(guard.address, true)); - expect(await guard.isScoped(guard.address)).to.be.equals(true); + await guard.scopeTarget(guard.address); + expect(await guard.isTargetScoped(guard.address)).to.be.equals(true); }); }); - describe("isfallbackAllowed()", async () => { - it("should return false if not set", async () => { - const { avatar, guard } = await setupTests(); + describe('isFallbackAllowed()', async () => { + it('should return false if not set', async () => { + const { guard } = await setupTests(); - expect(await guard.isfallbackAllowed(guard.address)).to.be.equals(false); + expect(await guard.isFallbackAllowed(guard.address)).to.be.equals(false); }); - it("should return false if set to false", async () => { + it('should return false if set to false', async () => { const { guard } = await setupTests(); - expect(guard.setFallbackAllowedOnTarget(guard.address, false)); - expect(await guard.isfallbackAllowed(guard.address)).to.be.equals(false); + expect(await guard.isFallbackAllowed(guard.address)).to.be.equals(false); + await guard.scopeAllowFallback(guard.address); + expect(await guard.isFallbackAllowed(guard.address)).to.be.equals(true); + await guard.scopeRevokeFallback(guard.address); + expect(await guard.isFallbackAllowed(guard.address)).to.be.equals(false); }); - it("should return true if set to true", async () => { + it('should return true if set to true', async () => { const { guard } = await setupTests(); - expect(await guard.isfallbackAllowed(guard.address)).to.be.equals(false); - expect(guard.setFallbackAllowedOnTarget(guard.address, true)); - expect(await guard.isfallbackAllowed(guard.address)).to.be.equals(true); + await guard.scopeAllowFallback(guard.address); + expect(await guard.isFallbackAllowed(guard.address)).to.be.equals(true); }); }); - describe("isAllowedFunction()", async () => { - it("should return false if not set", async () => { + describe('isFunctionAllowed()', async () => { + it('should return false if not set', async () => { const { avatar, guard } = await setupTests(); expect( - await guard.isAllowedFunction(avatar.address, "0x12345678") + await guard.isFunctionAllowed(avatar.address, '0x12345678') ).to.be.equals(false); }); - it("should return true if function is allowed", async () => { + it('should return true if function is allowed', async () => { const { guard } = await setupTests(); expect( - await guard.isAllowedFunction(guard.address, "0x12345678") + await guard.isFunctionAllowed(guard.address, '0x12345678') ).to.be.equals(false); - expect(guard.setAllowedFunction(guard.address, "0x12345678", true)) - .to.emit(guard, "SetFunctionAllowedOnTarget") - .withArgs(guard.address, "0x12345678", true); + expect(guard.scopeAllowFunction(guard.address, '0x12345678')) + .to.emit(guard, 'ScopeAllowFunction') + .withArgs(guard.address, '0x12345678'); expect( - await guard.isAllowedFunction(guard.address, "0x12345678") + await guard.isFunctionAllowed(guard.address, '0x12345678') ).to.be.equals(true); }); }); - - describe("isAllowedToDelegateCall()", async () => { - it("should return false if not set", async () => { - const { avatar, guard } = await setupTests(); - - expect(await guard.isAllowedTarget(avatar.address)).to.be.equals(false); - }); - - it("should return true if target is allowed to delegate call", async () => { - const { avatar, guard } = await setupTests(); - - expect(await guard.isAllowedToDelegateCall(avatar.address)).to.be.equals( - false - ); - expect(guard.setDelegateCallAllowedOnTarget(avatar.address, true)); - expect(await guard.isAllowedToDelegateCall(avatar.address)).to.be.equals( - true - ); - }); - }); });