import { expect } from "chai"; import { deployments, waffle } from "hardhat"; import "@nomiclabs/hardhat-ethers"; import { AddressZero } from "@ethersproject/constants"; import { getSafeTemplate, getSafeWithOwners } from "../utils/setup"; import { safeSignTypedData, executeTx, safeSignMessage, calculateSafeTransactionHash, safeApproveHash, buildSafeTransaction, logGas, calculateSafeDomainSeparator, preimageSafeTransactionHash, buildSignatureBytes } from "../../src/utils/execution"; import { chainId } from "../utils/encoding"; describe("GnosisSafe", async () => { const [user1, user2, user3, user4] = waffle.provider.getWallets() const setupTests = deployments.createFixture(async ({ deployments }) => { await deployments.fixture(); return { safe: await getSafeWithOwners([user1.address]) } }) describe("domainSeparator", async () => { it('should be correct according to EIP-712', async () => { const { safe } = await setupTests() const domainSeparator = calculateSafeDomainSeparator(safe, await chainId()) await expect( await safe.domainSeparator() ).to.be.eq(domainSeparator) }) }) describe("getTransactionHash", async () => { it('should correctly calculate EIP-712 hash', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const typedDataHash = calculateSafeTransactionHash(safe, tx, await chainId()) await expect( await safe.getTransactionHash( tx.to, tx.value, tx.data, tx.operation, tx.safeTxGas, tx.baseGas, tx.gasPrice, tx.gasToken, tx.refundReceiver, tx.nonce ) ).to.be.eq(typedDataHash) }) }) describe("getChainId", async () => { it('should return correct id', async () => { const { safe } = await setupTests() expect( await safe.getChainId() ).to.be.eq(await chainId()) }) }) describe("approveHash", async () => { it('approving should only be allowed for owners', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signerSafe = safe.connect(user2) await expect( signerSafe.approveHash(txHash) ).to.be.revertedWith("GS030") }) it('approving should emit event', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) await expect( safe.approveHash(txHash) ).emit(safe, "ApproveHash").withArgs(txHash, user1.address) }) }) describe("execTransaction", async () => { it('should fail if signature points into static part', async () => { const { safe } = await setupTests() const signatures = "0x" + "000000000000000000000000" + user1.address.slice(2) + "0000000000000000000000000000000000000000000000000000000000000020" + "00" + // r, s, v "0000000000000000000000000000000000000000000000000000000000000000" // Some data to read await expect( safe.execTransaction(safe.address, 0, "0x", 0, 0, 0, 0, AddressZero, AddressZero, signatures) ).to.be.revertedWith("GS021") }) it('should fail if sigantures data is not present', async () => { const { safe } = await setupTests() const signatures = "0x" + "000000000000000000000000" + user1.address.slice(2) + "0000000000000000000000000000000000000000000000000000000000000041" + "00" // r, s, v await expect( safe.execTransaction(safe.address, 0, "0x", 0, 0, 0, 0, AddressZero, AddressZero, signatures) ).to.be.revertedWith("GS022") }) it('should fail if sigantures data is too short', async () => { const { safe } = await setupTests() const signatures = "0x" + "000000000000000000000000" + user1.address.slice(2) + "0000000000000000000000000000000000000000000000000000000000000041" + "00" + // r, s, v "0000000000000000000000000000000000000000000000000000000000000020" // length await expect( safe.execTransaction(safe.address, 0, "0x", 0, 0, 0, 0, AddressZero, AddressZero, signatures) ).to.be.revertedWith("GS023") }) it('should be able to use EIP-712 for signature generation', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) await expect( logGas( "Execute cancel transaction with EIP-712 signature", executeTx(safe, tx, [await safeSignTypedData(user1, safe, tx)]) ) ).to.emit(safe, "ExecutionSuccess") }) it('should not be able to use different chainId for signing', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) await expect( executeTx(safe, tx, [await safeSignTypedData(user1, safe, tx, 1)]) ).to.be.revertedWith("GS026") }) it('should be able to use Signed Ethereum Messages for signature generation', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) await expect( logGas( "Execute cancel transaction with signed Ethereum message", executeTx(safe, tx, [await safeSignMessage(user1, safe, tx)]) ) ).to.emit(safe, "ExecutionSuccess") }) it('msg.sender does not need to approve before', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) await expect( logGas( "Without pre approved signature for msg.sender", executeTx(safe, tx, [await safeApproveHash(user1, safe, tx, true)]) ) ).to.emit(safe, "ExecutionSuccess") }) it('if not msg.sender on-chain approval is required', async () => { const { safe } = await setupTests() const user2Safe = safe.connect(user2) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) await expect( executeTx(user2Safe, tx, [await safeApproveHash(user1, safe, tx, true)]) ).to.be.revertedWith("GS025") }) it('should be able to use pre approved hashes for signature generation', async () => { const { safe } = await setupTests() const user2Safe = safe.connect(user2) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const approveHashSig = await safeApproveHash(user1, safe, tx) expect( await safe.approvedHashes(user1.address, txHash) ).to.be.eq(1) await expect( logGas( "With pre approved signature", executeTx(user2Safe, tx, [approveHashSig]) ) ).to.emit(safe, "ExecutionSuccess") // Approved hash should not reset automatically expect( await safe.approvedHashes(user1.address, txHash) ).to.be.eq(1) }) it('should revert if threshold is not set', async () => { await setupTests() const safe = await getSafeTemplate() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) await expect( executeTx(safe, tx, []) ).to.be.revertedWith("GS001") }) it('should revert if not the required amount of signature data is provided', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) await expect( executeTx(safe, tx, []) ).to.be.revertedWith("GS020") }) it('should not be able to use different signature type of same owner', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) await expect( executeTx(safe, tx, [await safeApproveHash(user1, safe, tx), await safeSignTypedData(user1, safe, tx), await safeSignTypedData(user3, safe, tx)]) ).to.be.revertedWith("GS026") }) it('should be able to mix all signature types', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address, user4.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) await expect( logGas( "Execute cancel transaction with 4 owners", executeTx(safe, tx, [ await safeApproveHash(user1, safe, tx, true), await safeApproveHash(user4, safe, tx), await safeSignTypedData(user2, safe, tx), await safeSignTypedData(user3, safe, tx) ]) ) ).to.emit(safe, "ExecutionSuccess") }) }) describe("checkSignatures", async () => { it('should fail if signature points into static part', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = "0x" + "000000000000000000000000" + user1.address.slice(2) + "0000000000000000000000000000000000000000000000000000000000000020" + "00" + // r, s, v "0000000000000000000000000000000000000000000000000000000000000000" // Some data to read await expect( safe.checkSignatures(txHash, txHashData, signatures) ).to.be.revertedWith("GS021") }) it('should fail if signatures data is not present', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = "0x" + "000000000000000000000000" + user1.address.slice(2) + "0000000000000000000000000000000000000000000000000000000000000041" + "00" // r, s, v await expect( safe.checkSignatures(txHash, txHashData, signatures) ).to.be.revertedWith("GS022") }) it('should fail if signatures data is too short', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = "0x" + "000000000000000000000000" + user1.address.slice(2) + "0000000000000000000000000000000000000000000000000000000000000041" + "00" + // r, s, v "0000000000000000000000000000000000000000000000000000000000000020" // length await expect( safe.checkSignatures(txHash, txHashData, signatures) ).to.be.revertedWith("GS023") }) it('should not be able to use different chainId for signing', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = buildSignatureBytes([await safeSignTypedData(user1, safe, tx, 1)]) await expect( safe.checkSignatures(txHash, txHashData, signatures) ).to.be.revertedWith("GS026") }) it('if not msg.sender on-chain approval is required', async () => { const { safe } = await setupTests() const user2Safe = safe.connect(user2) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = buildSignatureBytes([await safeApproveHash(user1, safe, tx, true)]) await expect( user2Safe.checkSignatures(txHash, txHashData, signatures) ).to.be.revertedWith("GS025") }) it('should revert if threshold is not set', async () => { await setupTests() const safe = await getSafeTemplate() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) await expect( safe.checkSignatures(txHash, txHashData, "0x") ).to.be.revertedWith("GS001") }) it('should revert if not the required amount of signature data is provided', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) await expect( safe.checkSignatures(txHash, txHashData, "0x") ).to.be.revertedWith("GS020") }) it('should not be able to use different signature type of same owner', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = buildSignatureBytes([ await safeApproveHash(user1, safe, tx), await safeSignTypedData(user1, safe, tx), await safeSignTypedData(user3, safe, tx) ]) await expect( safe.checkSignatures(txHash, txHashData, signatures) ).to.be.revertedWith("GS026") }) it('should be able to mix all signature types', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address, user4.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = buildSignatureBytes([ await safeApproveHash(user1, safe, tx, true), await safeApproveHash(user4, safe, tx), await safeSignTypedData(user2, safe, tx), await safeSignTypedData(user3, safe, tx) ]) await safe.checkSignatures(txHash, txHashData, signatures) }) }) describe("checkSignatures", async () => { it('should fail if signature points into static part', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = "0x" + "000000000000000000000000" + user1.address.slice(2) + "0000000000000000000000000000000000000000000000000000000000000020" + "00" + // r, s, v "0000000000000000000000000000000000000000000000000000000000000000" // Some data to read await expect( safe.checkNSignatures(txHash, txHashData, signatures, 1) ).to.be.revertedWith("GS021") }) it('should fail if signatures data is not present', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = "0x" + "000000000000000000000000" + user1.address.slice(2) + "0000000000000000000000000000000000000000000000000000000000000041" + "00" // r, s, v await expect( safe.checkNSignatures(txHash, txHashData, signatures, 1) ).to.be.revertedWith("GS022") }) it('should fail if signatures data is too short', async () => { const { safe } = await setupTests() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = "0x" + "000000000000000000000000" + user1.address.slice(2) + "0000000000000000000000000000000000000000000000000000000000000041" + "00" + // r, s, v "0000000000000000000000000000000000000000000000000000000000000020" // length await expect( safe.checkNSignatures(txHash, txHashData, signatures, 1) ).to.be.revertedWith("GS023") }) it('should not be able to use different chainId for signing', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = buildSignatureBytes([await safeSignTypedData(user1, safe, tx, 1)]) await expect( safe.checkNSignatures(txHash, txHashData, signatures, 1) ).to.be.revertedWith("GS026") }) it('if not msg.sender on-chain approval is required', async () => { const { safe } = await setupTests() const user2Safe = safe.connect(user2) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = buildSignatureBytes([await safeApproveHash(user1, safe, tx, true)]) await expect( user2Safe.checkNSignatures(txHash, txHashData, signatures, 1) ).to.be.revertedWith("GS025") }) it('should revert if not the required amount of signature data is provided', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) await expect( safe.checkNSignatures(txHash, txHashData, "0x", 1) ).to.be.revertedWith("GS020") }) it('should not be able to use different signature type of same owner', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = buildSignatureBytes([ await safeApproveHash(user1, safe, tx), await safeSignTypedData(user1, safe, tx), await safeSignTypedData(user3, safe, tx) ]) await expect( safe.checkNSignatures(txHash, txHashData, signatures, 3) ).to.be.revertedWith("GS026") }) it('should be able to mix all signature types', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address, user4.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = buildSignatureBytes([ await safeApproveHash(user1, safe, tx, true), await safeApproveHash(user4, safe, tx), await safeSignTypedData(user2, safe, tx), await safeSignTypedData(user3, safe, tx) ]) await safe.checkNSignatures(txHash, txHashData, signatures, 3) }) it('should be able to require no signatures', async () => { await setupTests() const safe = await getSafeTemplate() const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) await safe.checkNSignatures(txHash, txHashData, "0x", 0) }) it('should be able to require less signatures than the threshold', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address, user4.address]) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = buildSignatureBytes([ await safeSignTypedData(user3, safe, tx) ]) await safe.checkNSignatures(txHash, txHashData, signatures, 1) }) it('should be able to require more signatures than the threshold', async () => { await setupTests() const safe = await getSafeWithOwners([user1.address, user2.address, user3.address, user4.address], 2) const tx = buildSafeTransaction({ to: safe.address, nonce: await safe.nonce() }) const txHashData = preimageSafeTransactionHash(safe, tx, await chainId()) const txHash = calculateSafeTransactionHash(safe, tx, await chainId()) const signatures = buildSignatureBytes([ await safeApproveHash(user1, safe, tx, true), await safeApproveHash(user4, safe, tx), await safeSignTypedData(user2, safe, tx) ]) // Should fail as only 3 signaures are provided await expect( safe.checkNSignatures(txHash, txHashData, signatures, 4) ).to.be.revertedWith("GS020") await safe.checkNSignatures(txHash, txHashData, signatures, 3) }) }) })