Skip to content

Commit 27c1034

Browse files
committed
Add contracts, tests
1 parent b815ed3 commit 27c1034

File tree

4 files changed

+213
-5
lines changed

4 files changed

+213
-5
lines changed

contracts/NoDegegateCallGuard.sol

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.28;
3+
4+
import { BaseGuard } from "@safe-global/safe-contracts/contracts/base/GuardManager.sol";
5+
import { Enum } from "@safe-global/safe-contracts/contracts/common/Enum.sol";
6+
7+
contract NoDegegateCallGuard is BaseGuard {
8+
9+
error DelegateCallNotAllowed();
10+
11+
function checkTransaction(
12+
address /*to*/,
13+
uint256 /*value*/,
14+
bytes memory /*data*/,
15+
Enum.Operation operation,
16+
uint256 /*safeTxGas*/,
17+
uint256 /*baseGas*/,
18+
uint256 /*gasPrice*/,
19+
address /*gasToken*/,
20+
address payable /*refundReceiver*/,
21+
bytes memory /*signatures*/,
22+
address /*msgSender*/
23+
) external {
24+
if(operation == Enum.Operation.DelegateCall) {
25+
revert DelegateCallNotAllowed();
26+
}
27+
}
28+
29+
function checkAfterExecution(bytes32 txHash, bool success) external {
30+
31+
}
32+
}

contracts/SafeGuard.sol

Lines changed: 0 additions & 5 deletions
This file was deleted.

test/NoDelegateCall.test.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { ethers } from "hardhat";
2+
import { expect } from "chai";
3+
import { Contract, Signer, ZeroAddress } from "ethers";
4+
import { Safe, Safe__factory, SafeProxyFactory } from "../typechain-types";
5+
import { execTransaction } from "./utils/utils";
6+
7+
describe("Example module tests", async function () {
8+
let deployer: Signer;
9+
let alice: Signer;
10+
let masterCopy: Safe;
11+
let proxyFactory: SafeProxyFactory;
12+
let safeFactory: Safe__factory;
13+
let safe: Safe;
14+
let exampleGuard: Contract;
15+
16+
// Setup signers and deploy contracts before running tests
17+
before(async () => {
18+
[deployer, alice] = await ethers.getSigners();
19+
20+
safeFactory = await ethers.getContractFactory("Safe", deployer);
21+
masterCopy = await safeFactory.deploy();
22+
23+
proxyFactory = await (
24+
await ethers.getContractFactory("SafeProxyFactory", deployer)
25+
).deploy();
26+
});
27+
28+
// Setup contracts: Deploy a new token contract, create a new Safe, deploy the TokenWithdrawModule contract, and enable the module in the Safe.
29+
const setupContracts = async (
30+
walletOwners: Signer[],
31+
threshold: number
32+
) => {
33+
const ownerAddresses = await Promise.all(
34+
walletOwners.map(async (walletOwner) => await walletOwner.getAddress())
35+
);
36+
37+
const safeData = masterCopy.interface.encodeFunctionData("setup", [
38+
ownerAddresses,
39+
threshold,
40+
ZeroAddress,
41+
"0x",
42+
ZeroAddress,
43+
ZeroAddress,
44+
0,
45+
ZeroAddress,
46+
]);
47+
48+
// Read the safe address by executing the static call to createProxyWithNonce function
49+
const safeAddress = await proxyFactory.createProxyWithNonce.staticCall(
50+
await masterCopy.getAddress(),
51+
safeData,
52+
0n
53+
);
54+
55+
// Create the proxy with nonce
56+
await proxyFactory.createProxyWithNonce(
57+
await masterCopy.getAddress(),
58+
safeData,
59+
0n
60+
);
61+
62+
if (safeAddress === ZeroAddress) {
63+
throw new Error("Safe address not found");
64+
}
65+
66+
// Deploy the TokenWithdrawModule contract
67+
exampleGuard = await (
68+
await ethers.getContractFactory("NoDegegateCallGuard", deployer)
69+
).deploy();
70+
71+
const safe = await ethers.getContractAt("Safe", safeAddress);
72+
73+
// Enable the module in the safe
74+
const enableModuleData = masterCopy.interface.encodeFunctionData(
75+
"setGuard",
76+
[exampleGuard.target]
77+
);
78+
79+
// Execute the transaction to enable the module
80+
await execTransaction(
81+
walletOwners.slice(0, threshold),
82+
safe,
83+
safe.target,
84+
0,
85+
enableModuleData,
86+
0
87+
);
88+
};
89+
90+
// Test case to verify token transfer to bob
91+
it("Should successfully transfer tokens to bob", async function () {
92+
const wallets = [alice];
93+
await setupContracts(wallets, 1);
94+
// Execute the transaction to enable the module
95+
await expect ( execTransaction(
96+
wallets,
97+
safe,
98+
ZeroAddress,
99+
0,
100+
"0x",
101+
1
102+
)).to.be.revertedWithCustomError(exampleGuard, "DelegateCallNotAllowed");
103+
});
104+
});

test/utils/utils.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
2+
import { ethers } from "hardhat";
3+
import { Signer, AddressLike, BigNumberish, ZeroAddress } from "ethers";
4+
import { Safe } from "../../typechain-types";
5+
6+
/**
7+
* Executes a transaction on the Safe contract.
8+
* @param wallets - The signers of the transaction.
9+
* @param safe - The Safe contract instance.
10+
* @param to - The address to send the transaction to.
11+
* @param value - The value to send with the transaction.
12+
* @param data - The data to send with the transaction.
13+
* @param operation - The operation type (0 for call, 1 for delegate call).
14+
*/
15+
const execTransaction = async function (
16+
wallets: Signer[],
17+
safe: Safe,
18+
to: AddressLike,
19+
value: BigNumberish,
20+
data: string,
21+
operation: number,
22+
): Promise<void> {
23+
// Get the current nonce of the Safe contract
24+
const nonce = await safe.nonce();
25+
26+
// Get the transaction hash for the Safe transaction
27+
const transactionHash = await safe.getTransactionHash(
28+
to,
29+
value,
30+
data,
31+
operation,
32+
0,
33+
0,
34+
0,
35+
ZeroAddress,
36+
ZeroAddress,
37+
nonce
38+
);
39+
40+
let signatureBytes = "0x";
41+
const bytesDataHash = ethers.getBytes(transactionHash);
42+
43+
// Get the addresses of the signers
44+
const addresses = await Promise.all(wallets.map(wallet => wallet.getAddress()));
45+
// Sort the signers by their addresses
46+
const sorted = wallets.sort((a, b) => {
47+
const addressA = addresses[wallets.indexOf(a)];
48+
const addressB = addresses[wallets.indexOf(b)];
49+
return addressA.localeCompare(addressB, "en", { sensitivity: "base" });
50+
});
51+
52+
// Sign the transaction hash with each signer
53+
for (let i = 0; i < sorted.length; i++) {
54+
const flatSig = (await sorted[i].signMessage(bytesDataHash))
55+
.replace(/1b$/, "1f")
56+
.replace(/1c$/, "20");
57+
signatureBytes += flatSig.slice(2);
58+
}
59+
60+
// Execute the transaction on the Safe contract
61+
await safe.execTransaction(
62+
to,
63+
value,
64+
data,
65+
operation,
66+
0,
67+
0,
68+
0,
69+
ZeroAddress,
70+
ZeroAddress,
71+
signatureBytes
72+
);
73+
};
74+
75+
export {
76+
execTransaction,
77+
};

0 commit comments

Comments
 (0)