UNPKG

@safe-global/safe-contracts

Version:
179 lines (148 loc) 9.74 kB
import { expect } from "chai"; import hre, { deployments, waffle } from "hardhat"; import "@nomiclabs/hardhat-ethers"; import { deployContract, getMock, getMultiSend, getSafeWithOwners } from "../utils/setup"; import { buildContractCall, buildSafeTransaction, executeTx, MetaTransaction, safeApproveHash } from "../../src/utils/execution"; import { buildMultiSendSafeTx, encodeMultiSend } from "../../src/utils/multisend"; import { parseEther } from "@ethersproject/units"; describe("MultiSend", 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 getMultiSend(), mock: await getMock(), storageSetter, }; }); describe("multiSend", async () => { it("should enforce delegatecall to MultiSend", async () => { const { multiSend } = await setupTests(); const source = ` contract Test { function killme() public { selfdestruct(payable(msg.sender)); } }`; const killLib = await deployContract(user1, source); const nestedTransactionData = encodeMultiSend([buildContractCall(killLib, "killme", [], 0)]); const multiSendCode = await hre.ethers.provider.getCode(multiSend.address); await expect(multiSend.multiSend(nestedTransactionData)).to.be.revertedWith("MultiSend should only be called via delegatecall"); expect(await hre.ethers.provider.getCode(multiSend.address)).to.be.eq(multiSendCode); }); 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("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 contract delegatecalls", async () => { const { safe, multiSend, storageSetter } = await setupTests(); const txs: MetaTransaction[] = [buildContractCall(storageSetter, "setStorage", ["0xbaddad"], 0, true)]; 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" + "baddad".padEnd(64, "0")); await expect( await hre.ethers.provider.getStorageAt( storageSetter.address, "0x4242424242424242424242424242424242424242424242424242424242424242", ), ).to.be.eq("0x" + "".padEnd(64, "0")); }); it("can execute all calls in combination", 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, true), 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" + "baddad".padEnd(64, "0")); await expect( await hre.ethers.provider.getStorageAt( storageSetter.address, "0x4242424242424242424242424242424242424242424242424242424242424242", ), ).to.be.eq("0x" + "baddad".padEnd(64, "0")); }); }); });