@safe-global/safe-contracts
Version:
Ethereum multisig contract
150 lines (124 loc) • 8.27 kB
text/typescript
import { expect } from "chai";
import hre, { deployments, waffle } from "hardhat";
import "@nomiclabs/hardhat-ethers";
import { deployContract, getMock, getMultiSendCallOnly, getSafeWithOwners } from "../utils/setup";
import { buildContractCall, buildSafeTransaction, executeTx, MetaTransaction, safeApproveHash } from "../../src/utils/execution";
import { buildMultiSendSafeTx } from "../../src/utils/multisend";
import { parseEther } from "@ethersproject/units";
describe("MultiSendCallOnly", async () => {
const [user1, user2] = waffle.provider.getWallets();
const setupTests = deployments.createFixture(async ({ deployments }) => {
await deployments.fixture();
const setterSource = `
contract StorageSetter {
function setStorage(bytes3 data) public {
bytes32 slot = 0x4242424242424242424242424242424242424242424242424242424242424242;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, data)
}
}
}`;
const storageSetter = await deployContract(user1, setterSource);
return {
safe: await getSafeWithOwners([user1.address]),
multiSend: await getMultiSendCallOnly(),
mock: await getMock(),
storageSetter,
};
});
describe("multiSend", async () => {
it("Should fail when using invalid operation", async () => {
const { safe, multiSend } = await setupTests();
const txs = [buildSafeTransaction({ to: user2.address, operation: 2, nonce: 0 })];
const safeTx = buildMultiSendSafeTx(multiSend, txs, await safe.nonce());
await expect(executeTx(safe, safeTx, [await safeApproveHash(user1, safe, safeTx, true)])).to.revertedWith("GS013");
});
it("Should fail when using delegatecall operation", async () => {
const { safe, multiSend } = await setupTests();
const txs = [buildSafeTransaction({ to: user2.address, operation: 1, nonce: 0 })];
const safeTx = buildMultiSendSafeTx(multiSend, txs, await safe.nonce());
await expect(executeTx(safe, safeTx, [await safeApproveHash(user1, safe, safeTx, true)])).to.revertedWith("GS013");
});
it("Can execute empty multisend", async () => {
const { safe, multiSend } = await setupTests();
const txs: MetaTransaction[] = [];
const safeTx = buildMultiSendSafeTx(multiSend, txs, await safe.nonce());
await expect(executeTx(safe, safeTx, [await safeApproveHash(user1, safe, safeTx, true)])).to.emit(safe, "ExecutionSuccess");
});
it("Can execute single ether transfer", async () => {
const { safe, multiSend } = await setupTests();
await user1.sendTransaction({ to: safe.address, value: parseEther("1") });
const userBalance = await hre.ethers.provider.getBalance(user2.address);
await expect(await hre.ethers.provider.getBalance(safe.address)).to.be.deep.eq(parseEther("1"));
const txs: MetaTransaction[] = [buildSafeTransaction({ to: user2.address, value: parseEther("1"), nonce: 0 })];
const safeTx = buildMultiSendSafeTx(multiSend, txs, await safe.nonce());
await expect(executeTx(safe, safeTx, [await safeApproveHash(user1, safe, safeTx, true)])).to.emit(safe, "ExecutionSuccess");
await expect(await hre.ethers.provider.getBalance(safe.address)).to.be.deep.eq(parseEther("0"));
await expect(await hre.ethers.provider.getBalance(user2.address)).to.be.deep.eq(userBalance.add(parseEther("1")));
});
it("reverts all tx if any fails", async () => {
const { safe, multiSend } = await setupTests();
await user1.sendTransaction({ to: safe.address, value: parseEther("1") });
const userBalance = await hre.ethers.provider.getBalance(user2.address);
await expect(await hre.ethers.provider.getBalance(safe.address)).to.be.deep.eq(parseEther("1"));
const txs: MetaTransaction[] = [
buildSafeTransaction({ to: user2.address, value: parseEther("1"), nonce: 0 }),
buildSafeTransaction({ to: user2.address, value: parseEther("1"), nonce: 0 }),
];
const safeTx = buildMultiSendSafeTx(multiSend, txs, await safe.nonce(), { safeTxGas: 1 });
await expect(executeTx(safe, safeTx, [await safeApproveHash(user1, safe, safeTx, true)])).to.emit(safe, "ExecutionFailure");
await expect(await hre.ethers.provider.getBalance(safe.address)).to.be.deep.eq(parseEther("1"));
await expect(await hre.ethers.provider.getBalance(user2.address)).to.be.deep.eq(userBalance);
});
it("can be used when ETH is sent with execution", async () => {
const { safe, multiSend, storageSetter } = await setupTests();
const txs: MetaTransaction[] = [buildContractCall(storageSetter, "setStorage", ["0xbaddad"], 0)];
const safeTx = buildMultiSendSafeTx(multiSend, txs, await safe.nonce());
await expect(await hre.ethers.provider.getBalance(safe.address)).to.be.deep.eq(parseEther("0"));
await expect(executeTx(safe, safeTx, [await safeApproveHash(user1, safe, safeTx, true)], { value: parseEther("1") })).to.emit(
safe,
"ExecutionSuccess",
);
await expect(await hre.ethers.provider.getBalance(safe.address)).to.be.deep.eq(parseEther("1"));
});
it("can execute contract calls", async () => {
const { safe, multiSend, storageSetter } = await setupTests();
const txs: MetaTransaction[] = [buildContractCall(storageSetter, "setStorage", ["0xbaddad"], 0)];
const safeTx = buildMultiSendSafeTx(multiSend, txs, await safe.nonce());
await expect(executeTx(safe, safeTx, [await safeApproveHash(user1, safe, safeTx, true)])).to.emit(safe, "ExecutionSuccess");
await expect(
await hre.ethers.provider.getStorageAt(safe.address, "0x4242424242424242424242424242424242424242424242424242424242424242"),
).to.be.eq("0x" + "".padEnd(64, "0"));
await expect(
await hre.ethers.provider.getStorageAt(
storageSetter.address,
"0x4242424242424242424242424242424242424242424242424242424242424242",
),
).to.be.eq("0x" + "baddad".padEnd(64, "0"));
});
it("can execute combinations", async () => {
const { safe, multiSend, storageSetter } = await setupTests();
await user1.sendTransaction({ to: safe.address, value: parseEther("1") });
const userBalance = await hre.ethers.provider.getBalance(user2.address);
await expect(await hre.ethers.provider.getBalance(safe.address)).to.be.deep.eq(parseEther("1"));
const txs: MetaTransaction[] = [
buildSafeTransaction({ to: user2.address, value: parseEther("1"), nonce: 0 }),
buildContractCall(storageSetter, "setStorage", ["0xbaddad"], 0),
];
const safeTx = buildMultiSendSafeTx(multiSend, txs, await safe.nonce());
await expect(executeTx(safe, safeTx, [await safeApproveHash(user1, safe, safeTx, true)])).to.emit(safe, "ExecutionSuccess");
await expect(await hre.ethers.provider.getBalance(safe.address)).to.be.deep.eq(parseEther("0"));
await expect(await hre.ethers.provider.getBalance(user2.address)).to.be.deep.eq(userBalance.add(parseEther("1")));
await expect(
await hre.ethers.provider.getStorageAt(safe.address, "0x4242424242424242424242424242424242424242424242424242424242424242"),
).to.be.eq("0x" + "".padEnd(64, "0"));
await expect(
await hre.ethers.provider.getStorageAt(
storageSetter.address,
"0x4242424242424242424242424242424242424242424242424242424242424242",
),
).to.be.eq("0x" + "baddad".padEnd(64, "0"));
});
});
});