-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a82c0b6
commit dd1fd41
Showing
2 changed files
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
pragma solidity >=0.7.0 <0.9.0; | ||
|
||
import "../core/GuardableModifier.sol"; | ||
|
||
contract TestGuardableModifier is GuardableModifier { | ||
event executed( | ||
address to, | ||
uint256 value, | ||
bytes data, | ||
Enum.Operation operation, | ||
bool success | ||
); | ||
|
||
event executedAndReturnedData( | ||
address to, | ||
uint256 value, | ||
bytes data, | ||
Enum.Operation operation, | ||
bytes returnData, | ||
bool success | ||
); | ||
|
||
constructor(address _avatar, address _target) { | ||
bytes memory initParams = abi.encode(_avatar, _target); | ||
setUp(initParams); | ||
} | ||
|
||
/// @dev Passes a transaction to the modifier. | ||
/// @param to Destination address of module transaction | ||
/// @param value Ether value of module transaction | ||
/// @param data Data payload of module transaction | ||
/// @param operation Operation type of module transaction | ||
/// @notice Can only be called by enabled modules | ||
function execTransactionFromModule( | ||
address to, | ||
uint256 value, | ||
bytes calldata data, | ||
Enum.Operation operation | ||
) public override moduleOnly returns (bool success) { | ||
success = exec(to, value, data, operation); | ||
emit executed(to, value, data, operation, success); | ||
} | ||
|
||
/// @dev Passes a transaction to the modifier, expects return data. | ||
/// @param to Destination address of module transaction | ||
/// @param value Ether value of module transaction | ||
/// @param data Data payload of module transaction | ||
/// @param operation Operation type of module transaction | ||
/// @notice Can only be called by enabled modules | ||
function execTransactionFromModuleReturnData( | ||
address to, | ||
uint256 value, | ||
bytes calldata data, | ||
Enum.Operation operation | ||
) | ||
public | ||
override | ||
moduleOnly | ||
returns (bool success, bytes memory returnData) | ||
{ | ||
(success, returnData) = execAndReturnData(to, value, data, operation); | ||
emit executedAndReturnedData( | ||
to, | ||
value, | ||
data, | ||
operation, | ||
returnData, | ||
success | ||
); | ||
} | ||
|
||
function setUp(bytes memory initializeParams) public override initializer { | ||
setupModules(); | ||
__Ownable_init(); | ||
(address _avatar, address _target) = abi.decode( | ||
initializeParams, | ||
(address, address) | ||
); | ||
avatar = _avatar; | ||
target = _target; | ||
} | ||
|
||
function attemptToSetupModules() public { | ||
setupModules(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import hre from "hardhat"; | ||
import { expect } from "chai"; | ||
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; | ||
|
||
import { | ||
TestAvatar__factory, | ||
TestGuard__factory, | ||
TestGuardableModifier__factory, | ||
} from "../typechain-types"; | ||
|
||
describe("GuardableModifier", async () => { | ||
async function setupTests() { | ||
const [signer, someone, executor] = await hre.ethers.getSigners(); | ||
|
||
const Avatar = await hre.ethers.getContractFactory("TestAvatar"); | ||
const avatar = TestAvatar__factory.connect( | ||
(await Avatar.deploy()).address, | ||
signer | ||
); | ||
|
||
const Modifier = await hre.ethers.getContractFactory( | ||
"TestGuardableModifier" | ||
); | ||
const modifier = TestGuardableModifier__factory.connect( | ||
(await Modifier.connect(signer).deploy(avatar.address, avatar.address)) | ||
.address, | ||
signer | ||
); | ||
const Guard = await hre.ethers.getContractFactory("TestGuard"); | ||
const guard = TestGuard__factory.connect( | ||
(await Guard.deploy(modifier.address)).address, | ||
hre.ethers.provider | ||
); | ||
|
||
await avatar.enableModule(modifier.address); | ||
await modifier.enableModule(executor.address); | ||
|
||
return { | ||
avatar, | ||
someone, | ||
executor, | ||
guard, | ||
modifier, | ||
}; | ||
} | ||
|
||
describe("exec", async () => { | ||
it("skips guard pre-check if no guard is set", async () => { | ||
const { avatar, modifier, executor } = await loadFixture(setupTests); | ||
|
||
await expect( | ||
modifier | ||
.connect(executor) | ||
.execTransactionFromModule(avatar.address, 0, "0x", 0) | ||
).to.not.be.reverted; | ||
}); | ||
|
||
it("pre-checks transaction if guard is set", async () => { | ||
const { avatar, executor, modifier, guard } = await loadFixture( | ||
setupTests | ||
); | ||
await modifier.setGuard(guard.address); | ||
|
||
await expect( | ||
modifier | ||
.connect(executor) | ||
.execTransactionFromModule(avatar.address, 0, "0x", 0) | ||
) | ||
.to.emit(guard, "PreChecked") | ||
.withArgs(true); | ||
}); | ||
|
||
it("pre-checks and reverts transaction if guard is set", async () => { | ||
const { avatar, executor, modifier, guard } = await loadFixture( | ||
setupTests | ||
); | ||
await modifier.setGuard(guard.address); | ||
|
||
await expect( | ||
modifier | ||
.connect(executor) | ||
.execTransactionFromModule(avatar.address, 1337, "0x", 0) | ||
).to.be.revertedWith("Cannot send 1337"); | ||
}); | ||
|
||
it("skips post-check if no guard is enabled", async () => { | ||
const { avatar, executor, modifier, guard } = await loadFixture( | ||
setupTests | ||
); | ||
|
||
await expect( | ||
modifier | ||
.connect(executor) | ||
.execTransactionFromModule(avatar.address, 0, "0x", 0) | ||
).not.to.emit(guard, "PostChecked"); | ||
}); | ||
|
||
it("post-checks transaction if guard is set", async () => { | ||
const { avatar, executor, modifier, guard } = await loadFixture( | ||
setupTests | ||
); | ||
await modifier.setGuard(guard.address); | ||
|
||
await expect( | ||
modifier | ||
.connect(executor) | ||
.execTransactionFromModule(avatar.address, 0, "0x", 0) | ||
) | ||
.to.emit(guard, "PostChecked") | ||
.withArgs(true); | ||
}); | ||
}); | ||
|
||
describe("execAndReturnData", async () => { | ||
it("skips guard pre-check if no guard is set", async () => { | ||
const { avatar, modifier, executor } = await loadFixture(setupTests); | ||
|
||
await expect( | ||
modifier | ||
.connect(executor) | ||
.execTransactionFromModuleReturnData(avatar.address, 0, "0x", 0) | ||
).to.not.be.reverted; | ||
}); | ||
|
||
it("pre-checks transaction if guard is set", async () => { | ||
const { avatar, executor, modifier, guard } = await loadFixture( | ||
setupTests | ||
); | ||
await modifier.setGuard(guard.address); | ||
|
||
await expect( | ||
modifier | ||
.connect(executor) | ||
.execTransactionFromModuleReturnData(avatar.address, 0, "0x", 0) | ||
) | ||
.to.emit(guard, "PreChecked") | ||
.withArgs(true); | ||
}); | ||
|
||
it("pre-checks and reverts transaction if guard is set", async () => { | ||
const { avatar, executor, modifier, guard } = await loadFixture( | ||
setupTests | ||
); | ||
await modifier.setGuard(guard.address); | ||
|
||
await expect( | ||
modifier | ||
.connect(executor) | ||
.execTransactionFromModuleReturnData(avatar.address, 1337, "0x", 0) | ||
).to.be.revertedWith("Cannot send 1337"); | ||
}); | ||
|
||
it("skips post-check if no guard is enabled", async () => { | ||
const { avatar, executor, modifier, guard } = await loadFixture( | ||
setupTests | ||
); | ||
|
||
await expect( | ||
modifier | ||
.connect(executor) | ||
.execTransactionFromModuleReturnData(avatar.address, 0, "0x", 0) | ||
).not.to.emit(guard, "PostChecked"); | ||
}); | ||
|
||
it("post-checks transaction if guard is set", async () => { | ||
const { avatar, executor, modifier, guard } = await loadFixture( | ||
setupTests | ||
); | ||
await modifier.setGuard(guard.address); | ||
|
||
await expect( | ||
modifier | ||
.connect(executor) | ||
.execTransactionFromModuleReturnData(avatar.address, 0, "0x", 0) | ||
) | ||
.to.emit(guard, "PostChecked") | ||
.withArgs(true); | ||
}); | ||
}); | ||
}); |