<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Cizeon</title>
        <link>https://paragraph.com/@cizeon</link>
        <description>🕵️‍♂️ Hi! I'm a passionate blockchain enthusiast dedicating my time to discover and address security bugs.</description>
        <lastBuildDate>Sat, 23 May 2026 12:28:02 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Cizeon</title>
            <url>https://storage.googleapis.com/papyrus_images/0628a808d291cd6d6bd291468b4e3f20dde2ad54619934de6e222b07bf898838.png</url>
            <link>https://paragraph.com/@cizeon</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Gnosis Safe Internals — Part 3 — Signing transactions]]></title>
            <link>https://paragraph.com/@cizeon/gnosis-safe-internals-part-3-signing-transactions</link>
            <guid>3H297nN6QF11V4gb7i8i</guid>
            <pubDate>Wed, 02 Apr 2025 07:17:53 GMT</pubDate>
            <description><![CDATA[Finally, we’re diving into message signing. The core of the Gnosis Safe wallet. By the end of this article, you’ll be able to craft your own transactions and use a Gnosis Safe wallet without relying on any front-end! Freeeeeedom! :-) Check out the previous articles from this series:Gnosis Safe Internals — Part 1 — SafeProxyGnosis Safe Internals — Part 2 — OwnershipExecuting transactionsLet’s go back to Safe.sol and look at the execTransaction() function. This function executes the desired tra...]]></description>
            <content:encoded><![CDATA[<p>Finally, we’re diving into message signing. The core of the Gnosis Safe wallet. By the end of this article, you’ll be able to craft your own transactions and use a Gnosis Safe wallet without relying on any front-end! Freeeeeedom! :-)</p><p>Check out the previous articles from this series:</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="">Gnosis Safe Internals — Part 1 — SafeProxy</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/cizeon.eth/jWMMjs3aV8ud5ZowLNuMJHO3GkiAuJ442_j07H3AK0c">Gnosis Safe Internals — Part 2 — Ownership</a></p></li></ul><h1 id="h-executing-transactions" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Executing transactions</h1><p>Let’s go back to <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/Safe.sol#L111">Safe.sol</a> and look at the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/Safe.sol#L111">execTransaction() function</a>. This function executes the desired transaction on-chain, provided all necessary requirements are met.</p><pre data-type="codeBlock" text="function execTransaction(
    address to,
    uint256 value,
    bytes calldata data,
    Enum.Operation operation,
    uint256 safeTxGas,
    uint256 baseGas,
    uint256 gasPrice,
    address gasToken,
    address payable refundReceiver,
    bytes memory signatures
) external payable override returns (bool success) {
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">execTransaction</span>(<span class="hljs-params">
    <span class="hljs-keyword">address</span> to,
    <span class="hljs-keyword">uint256</span> value,
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data,
    Enum.Operation operation,
    <span class="hljs-keyword">uint256</span> safeTxGas,
    <span class="hljs-keyword">uint256</span> baseGas,
    <span class="hljs-keyword">uint256</span> gasPrice,
    <span class="hljs-keyword">address</span> gasToken,
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">payable</span> refundReceiver,
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> signatures
</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span> success</span>) </span>{
</code></pre><p>The arguments are straightforward:</p><ul><li><p>To: The address of the smart contract the wallet will interact with.</p></li><li><p>Data: The input data for the transaction.</p></li><li><p>Operation: 0 for a standard call, 1 for a delegatecall (be careful).</p></li><li><p>Gas-related variables: Parameters controlling gas usage.</p></li><li><p>Signatures: Approvals required to authorize the transaction.</p></li></ul><p>The first step is to compute the transaction hash. This is what is signed by the owners at the end.</p><pre data-type="codeBlock" text="txHash = getTransactionHash( // Transaction info
    to,
    value,
    data,
    operation,
    safeTxGas,
    // Payment info
    baseGas,
    gasPrice,
    gasToken,
    refundReceiver,
    // Signature info
    // We use the post-increment here, so the current nonce value is used and incremented afterwards.
    nonce++
);
"><code>txHash <span class="hljs-operator">=</span> getTransactionHash( <span class="hljs-comment">// Transaction info</span>
    to,
    value,
    data,
    operation,
    safeTxGas,
    <span class="hljs-comment">// Payment info</span>
    baseGas,
    gasPrice,
    gasToken,
    refundReceiver,
    <span class="hljs-comment">// Signature info</span>
    <span class="hljs-comment">// We use the post-increment here, so the current nonce value is used and incremented afterwards.</span>
    nonce<span class="hljs-operator">+</span><span class="hljs-operator">+</span>
);
</code></pre><p>This is nearly all the previous parameters and a nonce variable.</p><p>Let’s say we want to send 1 USDC on the Gnosis Safe chain to:</p><ul><li><p>cizeon.eth — 0xffF4aA9E37a3661db92d550936E5e27442aa5fa6</p></li></ul><p>The to argument is the address of the USDC token on the Gnosis chain:</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gnosisscan.io/address/0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0">0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0</a>.</p></li></ul><p>Since the second article in this series, we’ve learned how to construct the calldata:</p><pre data-type="codeBlock" text="$ cast calldata &quot;transfer(address,uint256)&quot; 0xffF4aA9E37a3661db92d550936E5e27442aa5fa6 1000000
0xa9059cbb000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa600000000000000000000000000000000000000000000000000000000000f4240
"><code><span class="hljs-variable">$ </span>cast calldata <span class="hljs-string">"transfer(address,uint256)"</span> <span class="hljs-number">0xffF4aA9E37a3661db92d550936E5e27442aa5fa6</span> <span class="hljs-number">1000000</span>
<span class="hljs-number">0xa9059cbb000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa600000000000000000000000000000000000000000000000000000000000f4240</span>
</code></pre><p>The operation variable is set to 0 since we’re making a standard call.</p><p>We’ll set all gas-related variables to 0 and set gasToken and refundReceiver to address(0).</p><p>To approve this transaction, we could simply sign all these parameters. This involves an owner using the private key of their externally owned address (EOA), their personal wallet, to generate a digital signature, ensuring the transaction’s authenticity and integrity.</p><p>However, this would be highly insecure. The recipient, cizeon.eth, could retrieve the transaction and its signature, then replay it to receive the USDC again and again. To prevent this replay attack, a nonce is used. A nonce is a unique number for each transaction, ensuring that once a transaction is executed, it cannot be used again.</p><p>To retrieve the nonce value to use, we can fetch it from the Safe wallet.</p><pre data-type="codeBlock" text="$ export ETH_RPC_URL=https://rpc.gnosischain.com
$ cast call 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D &quot;nonce()&quot;
0x0000000000000000000000000000000000000000000000000000000000000001
"><code><span class="hljs-meta prompt_">$ </span><span class="bash"><span class="hljs-built_in">export</span> ETH_RPC_URL=https://rpc.gnosischain.com</span>
<span class="hljs-meta prompt_">$ </span><span class="bash">cast call 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D <span class="hljs-string">"nonce()"</span></span>
0x0000000000000000000000000000000000000000000000000000000000000001
</code></pre><p>The nonce value here is one, this will be the first transaction from this Safe wallet.</p><h1 id="h-domain-hash" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Domain hash</h1><p>Now, let’s explore this <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/Safe.sol#L399C14-L399C32">getTransactionHash() function</a>. It follows the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-712">EIP-712</a> specifications for hashing typed data structures. However, we’ll go through this function line by line for a detailed explanation.</p><pre data-type="codeBlock" text="bytes32 domainHash = domainSeparator();
"><code>bytes32 <span class="hljs-attr">domainHash</span> = domainSeparator()<span class="hljs-comment">;</span>
</code></pre><p>First, it retrieves a domain hash from the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/Safe.sol#L384C1-L394C6">domainSeparator()</a> public view function.</p><pre data-type="codeBlock" text="// keccak256(
//     &quot;EIP712Domain(uint256 chainId,address verifyingContract)&quot;
// );
bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218;

function domainSeparator() public view override returns (bytes32) {
    uint256 chainId;
    /* solhint-disable no-inline-assembly */
    /// @solidity memory-safe-assembly
    assembly {
        chainId := chainid()
    }
    /* solhint-enable no-inline-assembly */
    return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, chainId, this));
}
"><code><span class="hljs-comment">// keccak256(</span>
<span class="hljs-comment">//     "EIP712Domain(uint256 chainId,address verifyingContract)"</span>
<span class="hljs-comment">// );</span>
<span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">constant</span> DOMAIN_SEPARATOR_TYPEHASH <span class="hljs-operator">=</span> <span class="hljs-number">0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">domainSeparator</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bytes32</span></span>) </span>{
    <span class="hljs-keyword">uint256</span> chainId;
    <span class="hljs-comment">/* solhint-disable no-inline-assembly */</span>
    <span class="hljs-comment">/// @solidity memory-safe-assembly</span>
    <span class="hljs-keyword">assembly</span> {
        chainId <span class="hljs-operator">:=</span> <span class="hljs-built_in">chainid</span>()
    }
    <span class="hljs-comment">/* solhint-enable no-inline-assembly */</span>
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(DOMAIN_SEPARATOR_TYPEHASH, chainId, <span class="hljs-built_in">this</span>));
}
</code></pre><p>The DOMAIN_SEPARATOR_TYPEHASH is just a hash of the EIP712Domain.</p><pre data-type="codeBlock" text="$ chisel
Welcome to Chisel! Type `!help` to show available commands.
➜ keccak256(&quot;EIP712Domain(uint256 chainId,address verifyingContract)&quot;)
Type: bytes32
└ Data: 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218
"><code>$ chisel
Welcome to Chisel<span class="hljs-operator">!</span> Type `<span class="hljs-operator">!</span>help` to show available commands.
➜ <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"EIP712Domain(uint256 chainId,address verifyingContract)"</span>)
Type: <span class="hljs-keyword">bytes32</span>
└ Data: <span class="hljs-number">0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218</span>
</code></pre><p>The chainId in Ethereum is a unique identifier for each Ethereum network or blockchain. If you recall the replay attack explained earlier, cizeon.eth could attempt to replay the transaction, even with the correct nonce, but on a different EVM blockchain. Assuming the target smart contract exists on that other chain. Which could also be malicious!</p><p>We can retrieve the chain id using <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cast/">cast</a>.</p><pre data-type="codeBlock" text="$ export ETH_RPC_URL=https://rpc.gnosischain.com
$ cast chain-id
100
"><code><span class="hljs-meta prompt_">$ </span><span class="bash"><span class="hljs-built_in">export</span> ETH_RPC_URL=https://rpc.gnosischain.com</span>
<span class="hljs-meta prompt_">$ </span><span class="bash">cast chain-id</span>
100
</code></pre><p>Lastly, we need the address of the Safe wallet. Again, we want to prevent cizeon.eth from replaying the transaction. Even with the correct nonce value and on the correct chain, if it targets a different Safe wallet owned by the same owner.</p><p>We can compute the domainHash ourselves.</p><pre data-type="codeBlock" text="$ chisel
➜ keccak256(abi.encode(0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218, 100, 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D))
Type: bytes32
└ Data: 0xd6092a6c53243ed8a77ab41647fd6b9c38df40eeaa8c1a259e87e620a9cba646
"><code>$ chisel
➜ <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(<span class="hljs-number">0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218</span>, <span class="hljs-number">100</span>, <span class="hljs-number">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</span>))
Type: <span class="hljs-keyword">bytes32</span>
└ Data: <span class="hljs-number">0xd6092a6c53243ed8a77ab41647fd6b9c38df40eeaa8c1a259e87e620a9cba646</span>
</code></pre><p>Since the domainSeparator() function is a public view, we could also have retrieve the domain hash from the wallet.</p><pre data-type="codeBlock" text="$ cast call 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D &quot;domainSeparator()&quot;
0xd6092a6c53243ed8a77ab41647fd6b9c38df40eeaa8c1a259e87e620a9cba646
"><code><span class="hljs-variable">$ </span>cast call <span class="hljs-number">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</span> <span class="hljs-string">"domainSeparator()"</span>
<span class="hljs-number">0xd6092a6c53243ed8a77ab41647fd6b9c38df40eeaa8c1a259e87e620a9cba646</span>
</code></pre><p>However, it’s better to do the maximum possible off-chain. Hopefully, the hashes match :-).</p><p>The domain hash is <strong>0xd6092a6c53243ed8a77ab41647fd6b9c38df40eeaa8c1a259e87e620a9cba646</strong></p><h1 id="h-message-hash" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Message hash</h1><p>Before we return to the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/Safe.sol#L399">getTransactionHash() function</a>, we need an additional key value: the SAFE_TX_TYPEHASH.</p><pre data-type="codeBlock" text="// keccak256(
//     &quot;SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)&quot;
// );
bytes32 private constant SAFE_TX_TYPEHASH = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8;
"><code><span class="hljs-comment">// keccak256(</span>
<span class="hljs-comment">//     "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)"</span>
<span class="hljs-comment">// );</span>
<span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">constant</span> SAFE_TX_TYPEHASH <span class="hljs-operator">=</span> <span class="hljs-number">0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8</span>;
</code></pre><p>The rest of the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/Safe.sol#L422">getTransactionHash() function</a> is written in assembly.</p><pre data-type="codeBlock" text="calldatacopy(ptr, data.offset, data.length)
let calldataHash := keccak256(ptr, data.length)
"><code>calldatacopy(ptr, data.offset, data.<span class="hljs-built_in">length</span>)
let calldataHash :<span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(ptr, data.<span class="hljs-built_in">length</span>)
</code></pre><p>First, it computes a hash of the calldata, which we crafted earlier to send 1 USDC to cizeon.eth.</p><pre data-type="codeBlock" text="$ chisel
keccak256(hex&quot;a9059cbb000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa600000000000000000000000000000000000000000000000000000000000f4240&quot;)
Type: bytes32
└ Data: 0x1133c5be11ee6385513e229829a8b3176a52ee02943ed2406ddf25beffe0b7ec
"><code>$ chisel
keccak256(<span class="hljs-built_in">hex</span><span class="hljs-string">"a9059cbb000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa600000000000000000000000000000000000000000000000000000000000f4240"</span>)
<span class="hljs-type">Type</span>: bytes32
└ Data: <span class="hljs-number">0x1133c5be11ee6385513e229829a8b3176a52ee02943ed2406ddf25beffe0b7ec</span>
</code></pre><p>It then combines all the variables and the SAFE_TX_TYPEHASH into a single packed structure.</p><pre data-type="codeBlock" text="mstore(ptr, SAFE_TX_TYPEHASH)
mstore(add(ptr, 32), to)
mstore(add(ptr, 64), value)
mstore(add(ptr, 96), calldataHash)
mstore(add(ptr, 128), operation)
mstore(add(ptr, 160), safeTxGas)
mstore(add(ptr, 192), baseGas)
mstore(add(ptr, 224), gasPrice)
mstore(add(ptr, 256), gasToken)
mstore(add(ptr, 288), refundReceiver)
mstore(add(ptr, 320), _nonce)
"><code><span class="hljs-built_in">mstore</span>(ptr, SAFE_TX_TYPEHASH)
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">32</span>), to)
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">64</span>), value)
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">96</span>), calldataHash)
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">128</span>), operation)
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">160</span>), safeTxGas)
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">192</span>), baseGas)
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">224</span>), gasPrice)
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">256</span>), gasToken)
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">288</span>), refundReceiver)
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">320</span>), _nonce)
</code></pre><p>The message hash is the result of hashing this structure. We can use chisel again to compute the hash of this structure ourselves.</p><pre data-type="codeBlock" text="$ chisel
➜ bytes32 safe_tx_typehash = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8;
➜ address to = 0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0;
➜ uint256 value = 0;
➜ bytes32 data_hashed = 0x1133c5be11ee6385513e229829a8b3176a52ee02943ed2406ddf25beffe0b7ec;
➜ uint8 operation = 0;
➜ uint256 safe_tx_gas = 0;
➜ uint256 base_gas = 0;
➜ uint256 gas_price = 0;
➜ address gas_token = address(0);
➜ address refund_receiver = address(0);
➜ uint256 nonce = 1;
➜ keccak256(abi.encode(safe_tx_typehash,to,value,data_hashed,operation,safe_tx_gas,base_gas,gas_price,gas_token,refund_receiver,nonce))
Type: bytes32
└ Data: 0x170fb87d7e389e2d5a22da94d29141f5e15bc4d316decc4d182060c8c2db84ba
"><code>$ chisel
➜ <span class="hljs-keyword">bytes32</span> safe_tx_typehash <span class="hljs-operator">=</span> <span class="hljs-number">0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8</span>;
➜ <span class="hljs-keyword">address</span> to <span class="hljs-operator">=</span> <span class="hljs-number">0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0</span>;
➜ <span class="hljs-keyword">uint256</span> value <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
➜ <span class="hljs-keyword">bytes32</span> data_hashed <span class="hljs-operator">=</span> <span class="hljs-number">0x1133c5be11ee6385513e229829a8b3176a52ee02943ed2406ddf25beffe0b7ec</span>;
➜ <span class="hljs-keyword">uint8</span> operation <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
➜ <span class="hljs-keyword">uint256</span> safe_tx_gas <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
➜ <span class="hljs-keyword">uint256</span> base_gas <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
➜ <span class="hljs-keyword">uint256</span> gas_price <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
➜ <span class="hljs-keyword">address</span> gas_token <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>);
➜ <span class="hljs-keyword">address</span> refund_receiver <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>);
➜ <span class="hljs-keyword">uint256</span> nonce <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
➜ <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(safe_tx_typehash,to,value,data_hashed,operation,safe_tx_gas,base_gas,gas_price,gas_token,refund_receiver,nonce))
Type: <span class="hljs-keyword">bytes32</span>
└ Data: <span class="hljs-number">0x170fb87d7e389e2d5a22da94d29141f5e15bc4d316decc4d182060c8c2db84ba</span>
</code></pre><p>The message hash is <strong>0x170fb87d7e389e2d5a22da94d29141f5e15bc4d316decc4d182060c8c2db84ba</strong></p><h1 id="h-safetxhash" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">safeTxHash</h1><p>Finally, we need to combine this hash with the EIP-712 prefix and the domain hash, as shown in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/Safe.sol#L456">getTransactionHash() function</a>.</p><pre data-type="codeBlock" text="// Step 3: Calculate the final EIP-712 hash
// First, hash the SafeTX struct (352 bytes total length)
mstore(add(ptr, 64), keccak256(ptr, 352))
// Store the EIP-712 prefix (0x1901), note that integers are left-padded
// so the EIP-712 encoded data starts at add(ptr, 30)
mstore(ptr, 0x1901)
// Store the domain separator
mstore(add(ptr, 32), domainHash)
// Calculate the hash
txHash := keccak256(add(ptr, 30), 66)
"><code><span class="hljs-comment">// Step 3: Calculate the final EIP-712 hash</span>
<span class="hljs-comment">// First, hash the SafeTX struct (352 bytes total length)</span>
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">64</span>), <span class="hljs-built_in">keccak256</span>(ptr, <span class="hljs-number">352</span>))
<span class="hljs-comment">// Store the EIP-712 prefix (0x1901), note that integers are left-padded</span>
<span class="hljs-comment">// so the EIP-712 encoded data starts at add(ptr, 30)</span>
<span class="hljs-built_in">mstore</span>(ptr, <span class="hljs-number">0</span>x1901)
<span class="hljs-comment">// Store the domain separator</span>
<span class="hljs-built_in">mstore</span>(add(ptr, <span class="hljs-number">32</span>), domainHash)
<span class="hljs-comment">// Calculate the hash</span>
txHash := <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">add</span>(ptr, <span class="hljs-number">30</span>), <span class="hljs-number">66</span>)
</code></pre><p>Chisel to the rescue again. Note that here we need to use abi.encodePacked() and not abi.encode().</p><pre data-type="codeBlock" text="$ chisel
➜ bytes2 prefix = 0x1901;
➜ bytes32 domain_hash = 0xd6092a6c53243ed8a77ab41647fd6b9c38df40eeaa8c1a259e87e620a9cba646;
➜ bytes32 message_hash = 0x170fb87d7e389e2d5a22da94d29141f5e15bc4d316decc4d182060c8c2db84ba;
➜ keccak256(abi.encodePacked(prefix,domain_hash,message_hash))
Type: bytes32
└ Data: 0x6403ca37fa9c54872e827f9da78cbc5818e7386a6bf5cbfb2788277b670ad80a
"><code>$ chisel
➜ <span class="hljs-keyword">bytes2</span> prefix <span class="hljs-operator">=</span> <span class="hljs-number">0x1901</span>;
➜ <span class="hljs-keyword">bytes32</span> domain_hash <span class="hljs-operator">=</span> <span class="hljs-number">0xd6092a6c53243ed8a77ab41647fd6b9c38df40eeaa8c1a259e87e620a9cba646</span>;
➜ <span class="hljs-keyword">bytes32</span> message_hash <span class="hljs-operator">=</span> <span class="hljs-number">0x170fb87d7e389e2d5a22da94d29141f5e15bc4d316decc4d182060c8c2db84ba</span>;
➜ <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(prefix,domain_hash,message_hash))
Type: <span class="hljs-keyword">bytes32</span>
└ Data: <span class="hljs-number">0x6403ca37fa9c54872e827f9da78cbc5818e7386a6bf5cbfb2788277b670ad80a</span>
</code></pre><p>The txHash is <strong>0x6403ca37fa9c54872e827f9da78cbc5818e7386a6bf5cbfb2788277b670ad80a</strong></p><p>This is all we need to sign the transaction :-)</p><h1 id="h-signing" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Signing</h1><p>The sub command with <code>cast</code> is <code>wallet sign</code>. There are several ways to use the private key:</p><ul><li><p>With <code>-i</code> (interactive mode), you simply need to copy and paste the private key.</p></li><li><p>Passing the private key or mnemonic as an argument, though this is not recommended.</p></li><li><p>Storing the key on the filesystem and protecting it with a password using <code>cast wallet import</code>.</p></li></ul><pre data-type="codeBlock" text="$ cast wallet sign 0x6403ca37fa9c54872e827f9da78cbc5818e7386a6bf5cbfb2788277b670ad80a -i --no-hash
0x75c0a770c7b65c23fa711c2c6e20332e7fa5c41f46e8f6cc0a089354974bed7d29f6b8e6606d2c11c3f03e7f406678bf40be41fa686d631a92a698becf7ef1df1c
"><code>$ cast wallet sign <span class="hljs-number">0x6403ca37fa9c54872e827f9da78cbc5818e7386a6bf5cbfb2788277b670ad80a</span> <span class="hljs-operator">-</span>i <span class="hljs-operator">-</span><span class="hljs-operator">-</span>no<span class="hljs-operator">-</span>hash
<span class="hljs-number">0x75c0a770c7b65c23fa711c2c6e20332e7fa5c41f46e8f6cc0a089354974bed7d29f6b8e6606d2c11c3f03e7f406678bf40be41fa686d631a92a698becf7ef1df1c</span>
</code></pre><p>With the signature, we can ask the Gnosis Safe wallet to execute the transaction. I suggest that we simulate the transaction first. This will be done forking the Gnosis chain locally. For that, we will use anvil, also part of the Foundry tool suite.</p><pre data-type="codeBlock" text="$ anvil --fork-url https://rpc.gnosischain.com
"><code>$ anvil <span class="hljs-operator">-</span><span class="hljs-operator">-</span>fork<span class="hljs-operator">-</span>url https:<span class="hljs-comment">//rpc.gnosischain.com</span>
</code></pre><p>This creates a new local RPC on 127.0.0.1:8545 by default.</p><p>Let’s simulate the final transaction:</p><pre data-type="codeBlock" text="export ETH_RPC_URL=http://127.0.0.1:8545
"><code><span class="hljs-keyword">export</span> ETH_RPC_URL=http:<span class="hljs-comment">//127.0.0.1:8545</span>
</code></pre><p>Since the command line is quite long, I recommend creating a simple script to streamline the process. The trace argument will be valuable for understanding the simulated transaction.</p><pre data-type="codeBlock" text="export ETH_RPC_URL=http://127.0.0.1:8545

SAFE_WALLET=0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D
TO=0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0
VALUE=0
DATA=0xa9059cbb000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa600000000000000000000000000000000000000000000000000000000000f4240
OPERATION=0
SAFE_TX_GAS=0
BASE_GAS=0
GAS_PRICE=0
GAS_TOKEN=0x0000000000000000000000000000000000000000
REFUND_RECEIVER=0x0000000000000000000000000000000000000000
SIGNATURE=0x75c0a770c7b65c23fa711c2c6e20332e7fa5c41f46e8f6cc0a089354974bed7d29f6b8e6606d2c11c3f03e7f406678bf40be41fa686d631a92a698becf7ef1df1c
cast call --trace \
    $SAFE_WALLET \
    &quot;execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)&quot; \
    $TO \
    $VALUE \
    $DATA \
    $OPERATION \
    $SAFE_TX_GAS \
    $BASE_GAS \
    $GAS_PRICE \
    $GAS_TOKEN \
    $REFUND_RECEIVER \
    $SIGNATURE
"><code>export <span class="hljs-attr">ETH_RPC_URL</span>=http://<span class="hljs-number">127.0</span>.<span class="hljs-number">0.1</span>:<span class="hljs-number">8545</span>

<span class="hljs-attr">SAFE_WALLET</span>=<span class="hljs-number">0</span>xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D
<span class="hljs-attr">TO</span>=<span class="hljs-number">0</span>x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0
<span class="hljs-attr">VALUE</span>=<span class="hljs-number">0</span>
<span class="hljs-attr">DATA</span>=<span class="hljs-number">0</span>xa9059cbb000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa600000000000000000000000000000000000000000000000000000000000f4240
<span class="hljs-attr">OPERATION</span>=<span class="hljs-number">0</span>
<span class="hljs-attr">SAFE_TX_GAS</span>=<span class="hljs-number">0</span>
<span class="hljs-attr">BASE_GAS</span>=<span class="hljs-number">0</span>
<span class="hljs-attr">GAS_PRICE</span>=<span class="hljs-number">0</span>
<span class="hljs-attr">GAS_TOKEN</span>=<span class="hljs-number">0</span>x0000000000000000000000000000000000000000
<span class="hljs-attr">REFUND_RECEIVER</span>=<span class="hljs-number">0</span>x0000000000000000000000000000000000000000
<span class="hljs-attr">SIGNATURE</span>=<span class="hljs-number">0</span>x75c0a770c7b65c23fa711c2c6e20332e7fa5c41f46e8f6cc0a089354974bed7d29f6b8e6606d2c11c3f03e7f406678bf40be41fa686d631a92a698becf7ef1df1c
cast call --trace \
    $SAFE_WALLET \
    "execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)" \
    $TO \
    $VALUE \
    $DATA \
    $OPERATION \
    $SAFE_TX_GAS \
    $BASE_GAS \
    $GAS_PRICE \
    $GAS_TOKEN \
    $REFUND_RECEIVER \
    $SIGNATURE
</code></pre><p>The result is quite verbose, so I’ve only selected the most relevant part.</p><pre data-type="codeBlock" text="❯ sh exec.sh
Traces:
  [59797] 

[...]  
  0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0::transfer(0xffF4aA9E37a3661db92d550936E5e27442aa5fa6, 1000000 [1e6])
    │   │   ├─ [16263] 0x107CF7fb73EA48D1D200989b156Ce1894d7AfEC7::transfer(0xffF4aA9E37a3661db92d550936E5e27442aa5fa6, 1000000 [1e6]) [delegatecall]
    │   │   │   ├─ emit Transfer(param0: 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D, param1: 0xffF4aA9E37a3661db92d550936E5e27442aa5fa6, param2: 1000000 [1e6])
[...]
└─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001
Transaction successfully executed.
Gas used: 79781
"><code>❯ sh exec.sh
Traces:
  <span class="hljs-section">[59797]</span> 

<span class="hljs-section">[...]</span>  
  0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0::transfer(0xffF4aA9E37a3661db92d550936E5e27442aa5fa6, 1000000 <span class="hljs-section">[1e6]</span>)
    │   │   ├─ <span class="hljs-section">[16263]</span> 0x107CF7fb73EA48D1D200989b156Ce1894d7AfEC7::transfer(0xffF4aA9E37a3661db92d550936E5e27442aa5fa6, 1000000 <span class="hljs-section">[1e6]</span>) <span class="hljs-section">[delegatecall]</span>
    │   │   │   ├─ emit Transfer(param0: 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D, param1: 0xffF4aA9E37a3661db92d550936E5e27442aa5fa6, param2: 1000000 <span class="hljs-section">[1e6]</span>)
<span class="hljs-section">[...]</span>
└─ ← <span class="hljs-section">[Return]</span> 0x0000000000000000000000000000000000000000000000000000000000000001
Transaction successfully executed.
Gas used: 79781
</code></pre><p>We can see the call to USDC::transfer() with cizeon.eth as the recipient for 1 USDC. The final return value is 1, indicating success.</p><p>Now, it’s time to execute this on-chain. Instead of using <code>cast call</code>, we’ll use <code>cast send</code> and provide a private key.</p><pre data-type="codeBlock" text="export ETH_RPC_URL=https://rpc.gnosischain.com

SAFE_WALLET=0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D
TO=0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0
VALUE=0
DATA=0xa9059cbb000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa600000000000000000000000000000000000000000000000000000000000f4240
OPERATION=0
SAFE_TX_GAS=0
BASE_GAS=0
GAS_PRICE=0
GAS_TOKEN=0x0000000000000000000000000000000000000000
REFUND_RECEIVER=0x0000000000000000000000000000000000000000
SIGNATURE=0x75c0a770c7b65c23fa711c2c6e20332e7fa5c41f46e8f6cc0a089354974bed7d29f6b8e6606d2c11c3f03e7f406678bf40be41fa686d631a92a698becf7ef1df1c
cast send --interactive \
    $SAFE_WALLET \
    &quot;execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)&quot; \
    $TO \
    $VALUE \
    $DATA \
    $OPERATION \
    $SAFE_TX_GAS \
    $BASE_GAS \
    $GAS_PRICE \
    $GAS_TOKEN \
    $REFUND_RECEIVER \
    $SIGNATURE
"><code>export <span class="hljs-attr">ETH_RPC_URL</span>=https://rpc.gnosischain.com

<span class="hljs-attr">SAFE_WALLET</span>=<span class="hljs-number">0</span>xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D
<span class="hljs-attr">TO</span>=<span class="hljs-number">0</span>x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0
<span class="hljs-attr">VALUE</span>=<span class="hljs-number">0</span>
<span class="hljs-attr">DATA</span>=<span class="hljs-number">0</span>xa9059cbb000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa600000000000000000000000000000000000000000000000000000000000f4240
<span class="hljs-attr">OPERATION</span>=<span class="hljs-number">0</span>
<span class="hljs-attr">SAFE_TX_GAS</span>=<span class="hljs-number">0</span>
<span class="hljs-attr">BASE_GAS</span>=<span class="hljs-number">0</span>
<span class="hljs-attr">GAS_PRICE</span>=<span class="hljs-number">0</span>
<span class="hljs-attr">GAS_TOKEN</span>=<span class="hljs-number">0</span>x0000000000000000000000000000000000000000
<span class="hljs-attr">REFUND_RECEIVER</span>=<span class="hljs-number">0</span>x0000000000000000000000000000000000000000
<span class="hljs-attr">SIGNATURE</span>=<span class="hljs-number">0</span>x75c0a770c7b65c23fa711c2c6e20332e7fa5c41f46e8f6cc0a089354974bed7d29f6b8e6606d2c11c3f03e7f406678bf40be41fa686d631a92a698becf7ef1df1c
cast send --interactive \
    $SAFE_WALLET \
    "execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)" \
    $TO \
    $VALUE \
    $DATA \
    $OPERATION \
    $SAFE_TX_GAS \
    $BASE_GAS \
    $GAS_PRICE \
    $GAS_TOKEN \
    $REFUND_RECEIVER \
    $SIGNATURE
</code></pre><p>Once again, the output of the command is quite verbose, so I’ve only selected the relevant part.</p><pre data-type="codeBlock" text="blockHash            0xd101e96ebdc4e182b0d8f5679152d26676b8a0cb3ac0dc5331e9832b06810cd8
blockNumber          39232811
"><code></code></pre><p>The full transaction can be viewed on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gnosisscan.io/tx/0xf822828a96202b525b7b250fa5e90b4983699d7b0ab987bf3441b269d4467bb9">GnosisScan</a>.</p><p>For this article, we used a private key in interactive mode. This is not recommended for production, as if your computer is compromised, malware could access the private key from memory and sign transactions on your behalf. Instead, using a hardware wallet is highly recommended.</p><h1 id="h-signing-using-a-ledger-key" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Signing using a Ledger key</h1><p>Personally, I only own a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.ledger.com/">Ledger device</a>, so I’ve focused on signing with a Ledger key.</p><p>Directly signing the safeTxHash is not supported by the Ledger key.</p><pre data-type="codeBlock" text="$ cast wallet sign 0x6403ca37fa9c54872e827f9da78cbc5818e7386a6bf5cbfb2788277b670ad80a --ledger --no-hash
Error: operation `sign_hash` is not supported by the signer
"><code>$ cast wallet sign <span class="hljs-number">0x6403ca37fa9c54872e827f9da78cbc5818e7386a6bf5cbfb2788277b670ad80a</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span>ledger <span class="hljs-operator">-</span><span class="hljs-operator">-</span>no<span class="hljs-operator">-</span>hash
<span class="hljs-built_in">Error</span>: operation `sign_hash` <span class="hljs-keyword">is</span> not supported by the signer
</code></pre><p>However, if provided in JSON format, the Ledger device understand <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-712">EIP-712</a> and will gladly compute the message hash and the safeTxHash for you.</p><pre data-type="codeBlock" text="{
  &quot;domain&quot;: {
    &quot;chainId&quot;: 100,
    &quot;verifyingContract&quot;: &quot;0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D&quot;
  },
  &quot;message&quot;: {
    &quot;to&quot;: &quot;0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0&quot;,
    &quot;value&quot;: 0,
    &quot;data&quot;: &quot;0xa9059cbb000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa600000000000000000000000000000000000000000000000000000000000f4240&quot;,
    &quot;operation&quot;: 0,
    &quot;safeTxGas&quot;: 0,
    &quot;baseGas&quot;: 0,
    &quot;gasPrice&quot;: 0,
    &quot;gasToken&quot;: &quot;0x0000000000000000000000000000000000000000&quot;,
    &quot;refundReceiver&quot;: &quot;0x0000000000000000000000000000000000000000&quot;,
    &quot;nonce&quot;: 1
  },
  &quot;primaryType&quot;: &quot;SafeTx&quot;,
  &quot;types&quot;: {
    &quot;EIP712Domain&quot;: [
      {
        &quot;name&quot;: &quot;chainId&quot;,
        &quot;type&quot;: &quot;uint256&quot;
      },
      {
        &quot;name&quot;: &quot;verifyingContract&quot;,
        &quot;type&quot;: &quot;address&quot;
      }
    ],
    &quot;SafeTx&quot;: [
      { &quot;name&quot;: &quot;to&quot;, &quot;type&quot;: &quot;address&quot; },
      { &quot;name&quot;: &quot;value&quot;, &quot;type&quot;: &quot;uint256&quot; },
      { &quot;name&quot;: &quot;data&quot;, &quot;type&quot;: &quot;bytes&quot; },
      { &quot;name&quot;: &quot;operation&quot;, &quot;type&quot;: &quot;uint8&quot; },
      { &quot;name&quot;: &quot;safeTxGas&quot;, &quot;type&quot;: &quot;uint256&quot; },
      { &quot;name&quot;: &quot;baseGas&quot;, &quot;type&quot;: &quot;uint256&quot; },
      { &quot;name&quot;: &quot;gasPrice&quot;, &quot;type&quot;: &quot;uint256&quot; },
      { &quot;name&quot;: &quot;gasToken&quot;, &quot;type&quot;: &quot;address&quot; },
      { &quot;name&quot;: &quot;refundReceiver&quot;, &quot;type&quot;: &quot;address&quot; },
      { &quot;name&quot;: &quot;nonce&quot;, &quot;type&quot;: &quot;uint256&quot; }
    ]
  }
}
"><code>{
  "domain": {
    "chainId": <span class="hljs-number">100</span>,
    <span class="hljs-string">"verifyingContract"</span>: <span class="hljs-string">"0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D"</span>
  },
  "message": {
    "<span class="hljs-selector-tag">to</span>": <span class="hljs-string">"0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0"</span>,
    <span class="hljs-string">"value"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-string">"data"</span>: <span class="hljs-string">"0xa9059cbb000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa600000000000000000000000000000000000000000000000000000000000f4240"</span>,
    <span class="hljs-string">"operation"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-string">"safeTxGas"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-string">"baseGas"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-string">"gasPrice"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-string">"gasToken"</span>: <span class="hljs-string">"0x0000000000000000000000000000000000000000"</span>,
    <span class="hljs-string">"refundReceiver"</span>: <span class="hljs-string">"0x0000000000000000000000000000000000000000"</span>,
    <span class="hljs-string">"nonce"</span>: <span class="hljs-number">1</span>
  },
  "primaryType": <span class="hljs-string">"SafeTx"</span>,
  <span class="hljs-string">"types"</span>: {
    "EIP712Domain": [
      {
        "name": <span class="hljs-string">"chainId"</span>,
        <span class="hljs-string">"type"</span>: <span class="hljs-string">"uint256"</span>
      },
      {
        "name": <span class="hljs-string">"verifyingContract"</span>,
        <span class="hljs-string">"type"</span>: <span class="hljs-string">"address"</span>
      }
    ],
    "SafeTx": [
      { "name": <span class="hljs-string">"to"</span>, <span class="hljs-string">"type"</span>: <span class="hljs-string">"address"</span> },
      { "name": <span class="hljs-string">"value"</span>, <span class="hljs-string">"type"</span>: <span class="hljs-string">"uint256"</span> },
      { "name": <span class="hljs-string">"data"</span>, <span class="hljs-string">"type"</span>: <span class="hljs-string">"bytes"</span> },
      { "name": <span class="hljs-string">"operation"</span>, <span class="hljs-string">"type"</span>: <span class="hljs-string">"uint8"</span> },
      { "name": <span class="hljs-string">"safeTxGas"</span>, <span class="hljs-string">"type"</span>: <span class="hljs-string">"uint256"</span> },
      { "name": <span class="hljs-string">"baseGas"</span>, <span class="hljs-string">"type"</span>: <span class="hljs-string">"uint256"</span> },
      { "name": <span class="hljs-string">"gasPrice"</span>, <span class="hljs-string">"type"</span>: <span class="hljs-string">"uint256"</span> },
      { "name": <span class="hljs-string">"gasToken"</span>, <span class="hljs-string">"type"</span>: <span class="hljs-string">"address"</span> },
      { "name": <span class="hljs-string">"refundReceiver"</span>, <span class="hljs-string">"type"</span>: <span class="hljs-string">"address"</span> },
      { "name": <span class="hljs-string">"nonce"</span>, <span class="hljs-string">"type"</span>: <span class="hljs-string">"uint256"</span> }
    ]
  }
}
</code></pre><pre data-type="codeBlock" text="$ cast wallet sign --ledger --mnemonic-index 1 --data --from-file transaction.json
0x8a14302c34d390ef6bee8291181677fd1dbbe9a483b1d0c1207b487cb58f54a463bd194075e2917a80258d6596b391ada5831daf4d5bc84b2e6d5d884cb242501b
"><code>$ cast wallet sign <span class="hljs-operator">-</span><span class="hljs-operator">-</span>ledger <span class="hljs-operator">-</span><span class="hljs-operator">-</span>mnemonic<span class="hljs-operator">-</span>index <span class="hljs-number">1</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span>data <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-keyword">from</span><span class="hljs-operator">-</span>file transaction.json
<span class="hljs-number">0x8a14302c34d390ef6bee8291181677fd1dbbe9a483b1d0c1207b487cb58f54a463bd194075e2917a80258d6596b391ada5831daf4d5bc84b2e6d5d884cb242501b</span>
</code></pre><p>Make sure to verify the domain hash and message hash displayed on the Ledger with the ones you’ve computed yourself.</p><p>And you now have the signature for your transaction: <strong>0x8a14302c34d390ef6bee8291181677fd1dbbe9a483b1d0c1207b487cb58f54a463bd194075e2917a80258d6596b391ada5831daf4d5bc84b2e6d5d884cb242501b</strong></p><h1 id="h-using-the-gnosis-safe-frontend" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Using the Gnosis Safe frontend</h1><p>If we had used the official Gnosis front-end, we would have obtained the same information.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/55adfe112366cb68c4faba9d68900cec55a293a5afa089bf4893d7b8a000f470.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><ol><li><p>The USDC recipient</p></li><li><p>The amount of USDC to send</p></li><li><p>The address of the USDC smart contract</p></li><li><p>Operation: call</p></li><li><p>The same domain hash as we have computed ourselves</p></li><li><p>The same message hash</p></li><li><p>The same safeTxHash</p></li></ol><p>And lastly, the same signature from the Ledger key.</p><p>That’s it for today! I hope you had as much fun reading this as I did writing it. In a future article, we’ll dive into how signature verification is handled in the smart contract.</p>]]></content:encoded>
            <author>cizeon@newsletter.paragraph.com (Cizeon)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/0c233bbe7d6d061c6b7bb58b99ffa69c0914ba3e08828f7de92c70032d295bc0.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Gnosis Safe Internals — Part 2 — Ownership]]></title>
            <link>https://paragraph.com/@cizeon/gnosis-safe-internals-part-2-ownership</link>
            <guid>xwPErb553PQy7fncQwiw</guid>
            <pubDate>Wed, 02 Apr 2025 07:13:01 GMT</pubDate>
            <description><![CDATA[We have seen in part 1 that a Gnosis Safe wallet is only a proxy contract that redirects all transaction to an implementation contract.Owners and thresholdThe implementation contract source code can be found on github.contract Safe is Singleton, NativeCurrencyPaymentFallback, ModuleManager, GuardManager, OwnerManager, SignatureDecoder, SecuredTokenTransfer, ISignatureValidatorConstants, FallbackManager, StorageAccessible, ISafe { The Safe contracts inherit from multiple other contracts, each ...]]></description>
            <content:encoded><![CDATA[<p>We have seen in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/cizeon.eth/Dyg1PznRPtcEwMPM-ZdWcPt7AkgaqewgYoGaB5Nab4E">part 1</a> that a Gnosis Safe wallet is only a proxy contract that redirects all transaction to an implementation contract.</p><h1 id="h-owners-and-threshold" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Owners and threshold</h1><p>The implementation contract source code can be found on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/Safe.sol#L37">github</a>.</p><pre data-type="codeBlock" text="contract Safe is
    Singleton,
    NativeCurrencyPaymentFallback,
    ModuleManager,
    GuardManager,
    OwnerManager,
    SignatureDecoder,
    SecuredTokenTransfer,
    ISignatureValidatorConstants,
    FallbackManager,
    StorageAccessible,
    ISafe
{
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Safe</span> <span class="hljs-keyword">is</span>
    <span class="hljs-title">Singleton</span>,
    <span class="hljs-title">NativeCurrencyPaymentFallback</span>,
    <span class="hljs-title">ModuleManager</span>,
    <span class="hljs-title">GuardManager</span>,
    <span class="hljs-title">OwnerManager</span>,
    <span class="hljs-title">SignatureDecoder</span>,
    <span class="hljs-title">SecuredTokenTransfer</span>,
    <span class="hljs-title">ISignatureValidatorConstants</span>,
    <span class="hljs-title">FallbackManager</span>,
    <span class="hljs-title">StorageAccessible</span>,
    <span class="hljs-title">ISafe</span>
</span>{
</code></pre><p>The Safe contracts inherit from multiple other contracts, each containing different wallet functionalities. Today, we will focus on the OwnerManager.</p><p>The Gnosis Safe wallet, often referred to as a multi-signature (or multi-sig) wallet, allows multiple owners to manage a single wallet. Transactions can only be executed once a predefined number of owners have approved them. This significantly reduces the risk of a compromised owner, as a quorum of owners must approve transactions.</p><p>The source code for the OwnerManager module is is located <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/base/OwnerManager.sol">here</a>.</p><p>Several storage variables are declared.</p><pre data-type="codeBlock" text="mapping(address =&gt; address) internal owners;
uint256 internal ownerCount;
uint256 internal threshold;
"><code><span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">address</span>) <span class="hljs-keyword">internal</span> owners;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">internal</span> ownerCount;
<span class="hljs-keyword">uint256</span> <span class="hljs-keyword">internal</span> threshold;
</code></pre><p>First, a mapping from address to address stores the list of owners. If you’re not familiar with Solidity&apos;s mappings, think of them as a HashMap or a dictionary in Python, where both the key and the value are addresses here.</p><p>The second variable, ownerCount, simply represents the number of owners.</p><p>Lastly, the threshold variable defines the number of owners required to approve a transaction. If set to 1, any owner can execute transactions. If set to 2, at least two owners must approve, regardless of the total number of owners.</p><p>This was simple. Let’s see how we can retrieve these variables on-chain.</p><h1 id="h-retrieving-the-data-on-chain" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Retrieving the data on-chain</h1><p>On <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/base/OwnerManager.sol#L120">line 120</a>, there’s a public view function that returns the threshold value. We’ll call it using the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cast/">cast</a> command from <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/">Foundry</a>. If you haven’t installed <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/">Foundry</a> yet, refer to the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/@cizeon/gnosis-safe-internals-part-1-safeproxy-3118101e5aed">first article</a> for instructions.</p><p>Pay close attention here. A solid understanding of this is essential for part 3 of the series.</p><p>On Ethereum, a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/en/developers/docs/transactions/">transaction</a> consists of several parameters. Here are the most important ones:</p><ul><li><p>from: The sender’s address</p></li><li><p>to: The recipient’s address</p></li><li><p>value: The amount of ETH to send, 0 if none.</p></li><li><p>input data: Optional field to include data.</p></li></ul><p>Addresses can also belong to smart contracts. For example, when sending ERC-20 tokens, you’re actually sending a transaction to the ERC-20 smart contract, which then calls the transfer() function with two parameters: the recipient (to) and the amount of tokens to send.</p><p>The function to call and its parameters are recorded in the input data field, known as calldata. This is the data passed along by our proxy contract.</p><p>To encode the calldata, we need the function’s signature. This consists of the function’s name and the data types of its parameters, separated by commas, no spaces. For the transfer() function of an ERC-20 token, the signature would be:</p><pre data-type="codeBlock" text="transfer(address,uint256)
"><code><span class="hljs-built_in">transfer</span>(address,uint256)
</code></pre><p>The function’s signature is then hashed using keccak256 and only the first 4 bytes of the hash are taken as function selector.</p><pre data-type="codeBlock" text="$ cast keccak256 &quot;transfer(address,uint256)&quot;
 0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b
"><code><span class="hljs-variable">$ </span>cast keccak256 <span class="hljs-string">"transfer(address,uint256)"</span>
 <span class="hljs-number">0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b</span>
</code></pre><p>The transfer function selector is: 0xa9059cbb. We would then append an address and an amount. Luckily we can use the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cast/">cast</a> command to pack this for us, and even send the transaction.</p><pre data-type="codeBlock" text="$ cast calldata &quot;transfer(address,uint256)&quot; 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D 10000000
0xa9059cbb000000000000000000000000e27f243cd5cb7364bbae758bb05aa62ec2a5fb7d0000000000000000000000000000000000000000000000000000000000989680
"><code><span class="hljs-variable">$ </span>cast calldata <span class="hljs-string">"transfer(address,uint256)"</span> <span class="hljs-number">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</span> <span class="hljs-number">10000000</span>
<span class="hljs-number">0xa9059cbb000000000000000000000000e27f243cd5cb7364bbae758bb05aa62ec2a5fb7d0000000000000000000000000000000000000000000000000000000000989680</span>
</code></pre><p>If this transaction is sent to the USDC smart contract, the calldata would be decoded as a call to the transfer() function, instructing it to send 10 USDC to the address 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D.</p><p>The observant reader might have guessed that, with just the calldata, it’s impossible to retrieve the function’s signature from the function selector. This is because there’s no way to reverse a hash back to its original data.</p><p>However, there are several databases of function selectors, and the most commonly used ones are stored. The transfer() function is on of them.</p><pre data-type="codeBlock" text="$ cast 4byte 0xa9059cbb
transfer(address,uint256)
"><code>$ cast <span class="hljs-number">4</span>byte <span class="hljs-number">0</span>xa9059cbb
<span class="hljs-built_in">transfer</span>(address,uint256)
</code></pre><p>We now have all the pieces needed to retrieve the threshold value from a Safe wallet. We’ll send a transaction to the wallet’s address (the proxy contract) which will borrow the code from the implementation contract containing the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/base/OwnerManager.sol#L13">OwnerManager</a> module. Are you following so far? 🙂</p><p>We can retrieve the function’s signature from the source code on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/base/OwnerManager.sol#L120">line 120</a>. There are no parameters.</p><pre data-type="codeBlock" text="getThreshold()
"><code><span class="hljs-built_in">getThreshold</span>()
</code></pre><p>We don’t need to manually compute the calldata; we’ll simply ask the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cast/">cast</a> command to send the transaction for us. Since it’s a public view function, no gas is required.</p><pre data-type="codeBlock" text="$ export ETH_RPC_URL=https://rpc.gnosischain.com
$ cast call 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D &quot;getThreshold()&quot;
0x0000000000000000000000000000000000000000000000000000000000000001
"><code><span class="hljs-meta prompt_">$ </span><span class="bash"><span class="hljs-built_in">export</span> ETH_RPC_URL=https://rpc.gnosischain.com</span>
<span class="hljs-meta prompt_">$ </span><span class="bash">cast call 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D <span class="hljs-string">"getThreshold()"</span></span>
0x0000000000000000000000000000000000000000000000000000000000000001
</code></pre><p>For this Gnosis Safe wallet, only one owner or signer is needed to meet the threshold.</p><p>Similarly, on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/base/OwnerManager.sol#L134">line 134</a>, there’s a public view function called getOwners(), which returns an array of addresses. To help <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cast/">cast</a> understand the return value, we can specify its type.</p><pre data-type="codeBlock" text="$ cast call 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D &quot;getOwners() returns (address[])&quot;
[0x6D5904167423e36A07427340d14804798039D4e6, 0xffF4aA9E37a3661db92d550936E5e27442aa5fa6]
"><code>$ cast call <span class="hljs-number">0</span>xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D "<span class="hljs-built_in">getOwners</span>() returns (address[])"
<span class="hljs-selector-attr">[0x6D5904167423e36A07427340d14804798039D4e6, 0xffF4aA9E37a3661db92d550936E5e27442aa5fa6]</span>
</code></pre><p>There are two owners, but only one is needed to execute a transaction from the wallet.</p><p>This is all we need to know to retrieve information on-chain from a wallet.</p><h1 id="h-how-does-it-work-internally" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How does it work internally?</h1><p>Since we’re here to learn and understand how things work, let’s take a look at the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/base/OwnerManager.sol#L13">OwnerManager</a> source code.</p><p>The list of owners is stored in an address to address mapping and unlike an array, it is not possible to simply loop through each elements of the array.</p><pre data-type="codeBlock" text="mapping(address =&gt; address) internal owners;
"><code><span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">address</span>) <span class="hljs-keyword">internal</span> owners;
</code></pre><p>The Gnosis Safe developers have done it in a very similar way that a linked list. A linked list is a data structure where each element contains a value and a reference to the next element in the sequence.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/bb05228f22e74016e9f827c7862e513d669839d5b0d333d38467641e0f345721.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Let’s observe the getOwners() funtion.</p><pre data-type="codeBlock" text="address internal constant SENTINEL_OWNERS = address(0x1);

function getOwners() public view override returns (address[] memory) {
    address[] memory array = new addressUnsupported embed;
    // populate return array
    uint256 index = 0;
    address currentOwner = owners[SENTINEL_OWNERS];
    while (currentOwner != SENTINEL_OWNERS) {
        array[index] = currentOwner;
        currentOwner = owners[currentOwner];
        ++index;
    }
    return array;
}
"><code><span class="hljs-keyword">address</span> <span class="hljs-keyword">internal</span> <span class="hljs-keyword">constant</span> SENTINEL_OWNERS <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0x1</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getOwners</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span>[] <span class="hljs-keyword">memory</span></span>) </span>{
    <span class="hljs-keyword">address</span>[] <span class="hljs-keyword">memory</span> array <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> addressUnsupported embed;
    <span class="hljs-comment">// populate return array</span>
    <span class="hljs-keyword">uint256</span> index <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
    <span class="hljs-keyword">address</span> currentOwner <span class="hljs-operator">=</span> owners[SENTINEL_OWNERS];
    <span class="hljs-keyword">while</span> (currentOwner <span class="hljs-operator">!</span><span class="hljs-operator">=</span> SENTINEL_OWNERS) {
        array[index] <span class="hljs-operator">=</span> currentOwner;
        currentOwner <span class="hljs-operator">=</span> owners[currentOwner];
        <span class="hljs-operator">+</span><span class="hljs-operator">+</span>index;
    }
    <span class="hljs-keyword">return</span> array;
}
</code></pre><ol><li><p>First, it reads the address stored in the mapping for address(0x1), which gives the address of the first owner.</p></li><li><p>Then, it reads the address stored at the first owner’s address, revealing the second owner’s address.</p></li><li><p>This process continues until the last owner points back to address(0x1).</p></li></ol><p>In this way, it’s possible to loop through each owner in the mapping while remaining gas-efficient.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b6e1e2ccff62e5595e02ff72f753b48cd7ba2e53e3be82a800faff4a1eed203d.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>As we saw in our <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/@cizeon/gnosis-safe-internals-part-1-safeproxy-3118101e5aed">first article</a>, the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cast/">cast</a> command allows us to retrieve on-chain storage, even if the value is private. Remember, with the EVM, nothing is truly private. But before we dive in, we need to understand how mappings are stored in storage.</p><p>The EVM storage is essentially a large array of 256-bit elements, starting at index 0 up to 2²⁵⁶. Each storage variables are stored one after each others. We can simply count each variables to find its slot number. This is why, to get the address of the singleton contract, we instructed <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cast/">cast</a> to fetch storage at index 0.</p><pre data-type="codeBlock" text="$ cast storage 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D 0
0x00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762
"><code><span class="hljs-variable">$ </span>cast storage <span class="hljs-number">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</span> <span class="hljs-number">0</span>
<span class="hljs-number">0x00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762</span>
</code></pre><p>If the source code of the smart-contract was also published to <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/">Etherscan</a> or <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gnosisscan.io/">Gnosisscan</a> here, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cast/">cast</a> can count the slots number for us.</p><pre data-type="codeBlock" text="$ export ETHERSCAN_API_KEY=**********************************
$ cast storage 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D --proxy 0x29fcb43b46531bca003ddc8fcb67ffe91900c762
╭-----------------------+------------------------------------------+------+
| Name                  | Type                                     | Slot |
+==========================================================================
| singleton             | address                                  | 0    |
|-----------------------+------------------------------------------+------+
| modules               | mapping(address =&gt; address)              | 1    |
|-----------------------+------------------------------------------+------+
| owners                | mapping(address =&gt; address)              | 2    |
|-----------------------+------------------------------------------+------+
| ownerCount            | uint256                                  | 3    |
|-----------------------+------------------------------------------+------+
| threshold             | uint256                                  | 4    |
|-----------------------+------------------------------------------+------+
| nonce                 | uint256                                  | 5    |
|-----------------------+------------------------------------------+------+
| _deprecatedDomainSeparator | bytes32                             | 6    |
|-----------------------+------------------------------------------+------+
| signedMessages        | mapping(bytes32 =&gt; uint256)              | 7    |
|-----------------------+------------------------------------------+------+
| approvedHashe  | mapping(address =&gt; mapping(bytes32 =&gt; uint256)) | 8    |
╰-----------------------+------------------------------------------+------+
"><code>$ export ETHERSCAN_API_KEY<span class="hljs-operator">=</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span>
$ cast <span class="hljs-keyword">storage</span> <span class="hljs-number">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span>proxy <span class="hljs-number">0x29fcb43b46531bca003ddc8fcb67ffe91900c762</span>
╭<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
<span class="hljs-operator">|</span> Name                  <span class="hljs-operator">|</span> Type                                     <span class="hljs-operator">|</span> Slot <span class="hljs-operator">|</span>
<span class="hljs-operator">+</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
<span class="hljs-operator">|</span> singleton             <span class="hljs-operator">|</span> <span class="hljs-keyword">address</span>                                  <span class="hljs-operator">|</span> <span class="hljs-number">0</span>    <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
<span class="hljs-operator">|</span> modules               <span class="hljs-operator">|</span> <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">address</span>)              <span class="hljs-operator">|</span> <span class="hljs-number">1</span>    <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
<span class="hljs-operator">|</span> owners                <span class="hljs-operator">|</span> <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">address</span>)              <span class="hljs-operator">|</span> <span class="hljs-number">2</span>    <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
<span class="hljs-operator">|</span> ownerCount            <span class="hljs-operator">|</span> <span class="hljs-keyword">uint256</span>                                  <span class="hljs-operator">|</span> <span class="hljs-number">3</span>    <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
<span class="hljs-operator">|</span> threshold             <span class="hljs-operator">|</span> <span class="hljs-keyword">uint256</span>                                  <span class="hljs-operator">|</span> <span class="hljs-number">4</span>    <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
<span class="hljs-operator">|</span> nonce                 <span class="hljs-operator">|</span> <span class="hljs-keyword">uint256</span>                                  <span class="hljs-operator">|</span> <span class="hljs-number">5</span>    <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
<span class="hljs-operator">|</span> _deprecatedDomainSeparator <span class="hljs-operator">|</span> <span class="hljs-keyword">bytes32</span>                             <span class="hljs-operator">|</span> <span class="hljs-number">6</span>    <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
<span class="hljs-operator">|</span> signedMessages        <span class="hljs-operator">|</span> <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">bytes32</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">uint256</span>)              <span class="hljs-operator">|</span> <span class="hljs-number">7</span>    <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
<span class="hljs-operator">|</span> approvedHashe  <span class="hljs-operator">|</span> <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">bytes32</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">uint256</span>)) <span class="hljs-operator">|</span> <span class="hljs-number">8</span>    <span class="hljs-operator">|</span>
╰<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span>
</code></pre><p>If the proxy argument isn’t available in your version of <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/">Foundry</a>, simply enter the address of the singleton contract instead. The command output here is truncated for clarity.</p><p>We now know that the owner’s mapping is stored in the second slot. However, the entire mapping can’t fit in 256 bits. In fact, this variable serves only as a placeholder, and what’s actually stored inside isn’t relevant.</p><pre data-type="codeBlock" text="$ cast storage 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D 2
0x0000000000000000000000000000000000000000000000000000000000000000
"><code><span class="hljs-variable">$ </span>cast storage <span class="hljs-number">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</span> <span class="hljs-number">2</span>
<span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000000</span>
</code></pre><p>Rather, the mapping’s values are stored in different slot numbers that we need to compute. For that we need to concatenate the key value with the mapping’s slot number, two in this case. The hash of it is used as the slot number. Let’s use the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/chisel/">chisel</a> command also from <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/">Foundry</a>. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/chisel/">Chisel</a> is a Solidity REPL (Read-Eval-Print-Loop) tool. It evaluates Solidity code and returns the result.</p><p>The first key value for our mapping is address(0x1), the SENTINEL_OWNERS constant. We need to concatenate it with the mapping’s slot number.</p><pre data-type="codeBlock" text="$ chisel
Welcome to Chisel! Type `!help` to show available commands.
➜ abi.encode(1,2)
Type: dynamic bytes
├ Hex (Memory):
├─ Length ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000040
├─ Contents ([0x20:..]): 0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002
├ Hex (Tuple Encoded):
├─ Pointer ([0x00:0x20]): 0x0000000000000000000000000000000000000000000000000000000000000020
├─ Length ([0x20:0x40]): 0x0000000000000000000000000000000000000000000000000000000000000040
└─ Contents ([0x40:..]): 0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002
"><code>$ chisel
Welcome to Chisel<span class="hljs-operator">!</span> Type `<span class="hljs-operator">!</span>help` to show available commands.
➜ <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(<span class="hljs-number">1</span>,<span class="hljs-number">2</span>)
Type: dynamic <span class="hljs-keyword">bytes</span>
├ Hex (Memory):
├─ Length ([<span class="hljs-number">0x00</span>:<span class="hljs-number">0x20</span>]): <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000040</span>
├─ Contents ([<span class="hljs-number">0x20</span>:..]): <span class="hljs-number">0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002</span>
├ Hex (Tuple Encoded):
├─ Pointer ([<span class="hljs-number">0x00</span>:<span class="hljs-number">0x20</span>]): <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000020</span>
├─ Length ([<span class="hljs-number">0x20</span>:<span class="hljs-number">0x40</span>]): <span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000040</span>
└─ Contents ([<span class="hljs-number">0x40</span>:..]): <span class="hljs-number">0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002</span>
</code></pre><p>Let’s compute the hash. This is our slot number.</p><pre data-type="codeBlock" text="➜ keccak256(abi.encode(1,2))
Type: bytes32
└ Data: 0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0
"><code>➜ <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(<span class="hljs-number">1</span>,<span class="hljs-number">2</span>))
Type: <span class="hljs-keyword">bytes32</span>
└ Data: <span class="hljs-number">0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0</span>
</code></pre><p>If correct that value at this slot number should be our first owner.</p><pre data-type="codeBlock" text="$ cast storage 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D 0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0
0x0000000000000000000000006d5904167423e36a07427340d14804798039d4e6
"><code><span class="hljs-variable">$ </span>cast storage <span class="hljs-number">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</span> <span class="hljs-number">0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0</span>
<span class="hljs-number">0x0000000000000000000000006d5904167423e36a07427340d14804798039d4e6</span>
</code></pre><p>If we want to retrieve the address of the second owner, we need to compute the slot number.</p><pre data-type="codeBlock" text="$ chisel
keccak256(abi.encode(0x6d5904167423e36a07427340d14804798039d4e6,2))
Type: bytes32
└ Data: 0x9af88d12773e5e18abf0cac977452a5c1dbfefcdef018154daf5409a67cd4bfe
"><code>$ chisel
<span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(<span class="hljs-number">0x6d5904167423e36a07427340d14804798039d4e6</span>,<span class="hljs-number">2</span>))
Type: <span class="hljs-keyword">bytes32</span>
└ Data: <span class="hljs-number">0x9af88d12773e5e18abf0cac977452a5c1dbfefcdef018154daf5409a67cd4bfe</span>
</code></pre><p>Let’s retrieve the second owner’s address from the contract’s storage.</p><pre data-type="codeBlock" text="$ cast storage 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D  0x9af88d12773e5e18abf0cac977452a5c1dbfefcdef018154daf5409a67cd4bfe
0x000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa6
"><code><span class="hljs-variable">$ </span>cast storage <span class="hljs-number">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</span>  <span class="hljs-number">0x9af88d12773e5e18abf0cac977452a5c1dbfefcdef018154daf5409a67cd4bfe</span>
<span class="hljs-number">0x000000000000000000000000fff4aa9e37a3661db92d550936e5e27442aa5fa6</span>
</code></pre><p>We already know there are only two owners for this wallet, the address of the third mapping slot should be address(0x1).</p><pre data-type="codeBlock" text="$ chisel
➜ keccak256(abi.encode(0xfff4aa9e37a3661db92d550936e5e27442aa5fa6,2))
Type: bytes32
└ Data: 0x7e0181350b9666b922b76c7918cbfa0c8b34d0de78f3a35620d03967f396f283
$ cast storage 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D 0x7e0181350b9666b922b76c7918cbfa0c8b34d0de78f3a35620d03967f396f283
0x0000000000000000000000000000000000000000000000000000000000000001
"><code>$ chisel
➜ <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(<span class="hljs-number">0xfff4aa9e37a3661db92d550936e5e27442aa5fa6</span>,<span class="hljs-number">2</span>))
Type: <span class="hljs-keyword">bytes32</span>
└ Data: <span class="hljs-number">0x7e0181350b9666b922b76c7918cbfa0c8b34d0de78f3a35620d03967f396f283</span>
$ cast <span class="hljs-keyword">storage</span> <span class="hljs-number">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</span> <span class="hljs-number">0x7e0181350b9666b922b76c7918cbfa0c8b34d0de78f3a35620d03967f396f283</span>
<span class="hljs-number">0x0000000000000000000000000000000000000000000000000000000000000001</span>
</code></pre><p>The loop is closed :-).</p><h1 id="h-changing-owners" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Changing owners</h1><p>Lastly, we can see there are a couple of internal functions to setup owners.</p><pre data-type="codeBlock" text="function setupOwners(address[] memory _owners, uint256 _threshold) internal {
function addOwnerWithThreshold(address owner, uint256 _threshold) public override authorized {
function removeOwner(address prevOwner, address owner, uint256 _threshold) public override authorized {
function swapOwner(address prevOwner, address oldOwner, address newOwner) public override authorized {
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setupOwners</span>(<span class="hljs-params"><span class="hljs-keyword">address</span>[] <span class="hljs-keyword">memory</span> _owners, <span class="hljs-keyword">uint256</span> _threshold</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addOwnerWithThreshold</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">uint256</span> _threshold</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title">authorized</span> </span>{
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">removeOwner</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> prevOwner, <span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">uint256</span> _threshold</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title">authorized</span> </span>{
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swapOwner</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> prevOwner, <span class="hljs-keyword">address</span> oldOwner, <span class="hljs-keyword">address</span> newOwner</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title">authorized</span> </span>{
</code></pre><p>They are all protected by the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/common/SelfAuthorized.sol#L15">authorized modifier</a>.</p><pre data-type="codeBlock" text="function requireSelfCall() private view {
    if (msg.sender != address(this)) revertWithError(&quot;GS031&quot;);
}

modifier authorized() {
    // Modifiers are copied around during compilation. This is a function call as it minimized the bytecode size
    requireSelfCall();
    _;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">requireSelfCall</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">private</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> </span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)) revertWithError(<span class="hljs-string">"GS031"</span>);
}

<span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">authorized</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Modifiers are copied around during compilation. This is a function call as it minimized the bytecode size</span>
    requireSelfCall();
    <span class="hljs-keyword">_</span>;
}
</code></pre><p>Only the Gnosis Safe wallet itself has the ability to change the owners.</p><p>Several other protections are also implemented:</p><pre data-type="codeBlock" text="// Owner address cannot be null, the sentinel or the Safe itself.
if (newOwner == address(0) || newOwner == SENTINEL_OWNERS || newOwner == address(this)) revertWithError(&quot;GS203&quot;);
"><code><span class="hljs-comment">// Owner address cannot be null, the sentinel or the Safe itself.</span>
<span class="hljs-keyword">if</span> (newOwner <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>) <span class="hljs-operator">|</span><span class="hljs-operator">|</span> newOwner <span class="hljs-operator">=</span><span class="hljs-operator">=</span> SENTINEL_OWNERS <span class="hljs-operator">|</span><span class="hljs-operator">|</span> newOwner <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)) revertWithError(<span class="hljs-string">"GS203"</span>);
</code></pre><p>An owner cannot be address(0x0) or address(0x1), nor can it be the wallet itself.</p><pre data-type="codeBlock" text="// No duplicate owners allowed.
if (owners[owner] != address(0)) revertWithError(&quot;GS204&quot;);
"><code><span class="hljs-comment">// No duplicate owners allowed.</span>
<span class="hljs-keyword">if</span> (owners[owner] <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) revertWithError(<span class="hljs-string">"GS204"</span>);
</code></pre><p>Duplicates owners are also not permitted.</p><p><strong>If the owner’s list needs to be updated, the transaction must go through the transaction signing process and reach the quorum to be executed on-chain.</strong></p><p>This is what we need to explore next: how transactions that must be executed by the smart contract are encoded, signed, and then executed.</p><p>We’ll dive into this in the next article of the series. Stay tuned!</p>]]></content:encoded>
            <author>cizeon@newsletter.paragraph.com (Cizeon)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/0c233bbe7d6d061c6b7bb58b99ffa69c0914ba3e08828f7de92c70032d295bc0.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Gnosis Safe Internals — Part 1 — SafeProxy]]></title>
            <link>https://paragraph.com/@cizeon/gnosis-safe-internals-part-1-safeproxy</link>
            <guid>IpIFH4ZxTuqFuJiYJ2eU</guid>
            <pubDate>Wed, 02 Apr 2025 07:05:59 GMT</pubDate>
            <description><![CDATA[The recent Bybit hack was a wake-up call, reminding everyone that security is never guaranteed and good practices are essential. A hardware wallet is crucial for protecting private keys from malware, but it’s not enough if we blindly sign whatever data our computer presents. This blog series explores the Gnosis Safe wallet and how it works, giving readers the chance to use it directly on-chain without relying on a front-end. If you want to follow along and experiment, I highly recommend insta...]]></description>
            <content:encoded><![CDATA[<p>The recent <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/Bybit_Official/status/1892965292931702929">Bybit hack</a> was a wake-up call, reminding everyone that security is never guaranteed and good practices are essential. A hardware wallet is crucial for protecting private keys from malware, but it’s not enough if we blindly sign whatever data our computer presents.</p><p>This blog series explores the Gnosis Safe wallet and how it works, giving readers the chance to use it directly on-chain without relying on a front-end.</p><p>If you want to follow along and experiment, I highly recommend installing <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/">Foundry</a>. It provides powerful commands like <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cast/">cast</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/chisel/">chisel</a>, which we’ll be using extensively.</p><p>Installation is straightforward, but refer to the official manual for full details:</p><pre data-type="codeBlock" text="curl -L https://foundry.paradigm.xyz | bash
"><code>curl <span class="hljs-operator">-</span>L https:<span class="hljs-comment">//foundry.paradigm.xyz | bash</span>
</code></pre><p>We’ll also be using the Gnosis Chain. You can set up a public RPC by exporting an environment variable:</p><pre data-type="codeBlock" text="export ETH_RPC_URL=https://rpc.gnosischain.com
"><code><span class="hljs-keyword">export</span> ETH_RPC_URL=https:<span class="hljs-comment">//rpc.gnosischain.com</span>
</code></pre><p>The first key concept is that a Gnosis Safe wallet is simply a proxy contract. It’s a minimal contract used only for storing data while borrowing code from other smart contracts. This design enables upgrades and enhances modularity.</p><p>There are several design patterns in Solidity for proxy contracts such as the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog.openzeppelin.com/the-transparent-proxy-pattern">Transparent Proxy Pattern</a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.openzeppelin.com/upgrades-plugins/proxies">Proxy Upgrade Pattern</a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-1967">EIP-1967</a> for storage collision, or the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-2535">Diamond Pattern</a>.</p><p>The approach used by Gnosis Safe can be observed from the SafeProxy.sol contract: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/proxies/SafeProxy.sol">https://github.com/safe-global/safe-smart-account/blob/main/contracts/proxies/SafeProxy.sol</a></p><p>The first storage slot stores the address of a singleton contract, which handles the execution of code.</p><pre data-type="codeBlock" text="// Singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated.
// To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt`
address internal singleton;
"><code><span class="hljs-comment">// Singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated.</span>
<span class="hljs-comment">// To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt`</span>
<span class="hljs-selector-tag">address</span> <span class="hljs-selector-tag">internal</span> <span class="hljs-selector-tag">singleton</span>;
</code></pre><p>Let’s investigate this Safe wallet deployed on Gnosis: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://app.safe.global/home?safe=gno%3A0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</a>.</p><p>The singleton contract’s address is stored in an internal variable called singleton. Fortunately, the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cast/">cast</a> command can retrieve this storage for us.</p><pre data-type="codeBlock" text="$ cast storage 0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D 0
0x00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762
"><code><span class="hljs-variable">$ </span>cast storage <span class="hljs-number">0xE27f243CD5CB7364Bbae758Bb05AA62ec2a5Fb7D</span> <span class="hljs-number">0</span>
<span class="hljs-number">0x00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762</span>
</code></pre><p>In the EVM, storage slots are 256 bits (32 bytes), while addresses are only 160 bits (20 bytes). To extract the address, we simply take the last 160 bits, which gives us <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gnosisscan.io/address/0x29fcb43b46531bca003ddc8fcb67ffe91900c762">0x29fcb43b46531bca003ddc8fcb67ffe91900c762</a> as the singleton’s address. This is indeed a SafeL2 contract.</p><p>Gnosis Safe implementation has two contracts: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/Safe.sol">Safe.sol</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/SafeL2.sol">SafeL2.sol</a>. The SafeL2 contract is identical to the Safe contract but also emits events on transaction execution. Since L2s are generally cheaper, the extra gas cost is acceptable. In this article, we’ll focus on the Safe contract.</p><p>We know that the address of a Gnosis Safe wallet corresponds to the proxy contract’s address. We also know how to retrieve the address of the implementation contract, which contains the code to be executed. But how does the proxy actually execute the code?</p><p>This is achieved through the fallback() function of the proxy contract, combined with the use of delegate calls.</p><p>In EVM smart contracts, a fallback() function is a special function that is triggered when a contract receives a transaction or call that doesn’t match any of its defined functions.</p><p>For example, if we attempt to call the transfer() function from an ERC-20 contract, but our contract doesn’t implement it, the fallback() function will be triggered. Assuming we have a fallback() function defined, of course.</p><p>If we try to call the approve() function and it’s also not implemented in our contract, the fallback() function will be triggered once again. It acts as a catch-all for any unmatched function calls.</p><p>The second concept to understand is a delegatecall(). It s a low-level function that allows a contract to execute code in the context of another contract. It typically borrows code from another smart contract, but all storage modifications are made in the calling contract: the proxy contract.</p><p>We have the two essentials components of a proxy smart contract. A catch all fallback() function and a way to borrow code from another smart contract.</p><p>From <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/safe-global/safe-smart-account/blob/main/contracts/proxies/SafeProxy.sol#L32C1-L61C6">SafeProxy.sol</a> on line 32:</p><pre data-type="codeBlock" text="/// @dev Fallback function forwards all transactions and returns all received return data.
fallback() external payable {
    // Note that this assembly block is **intentionally** not marked as memory-safe. First of all, it isn&apos;t memory
    // safe to begin with, and turning this into memory-safe assembly would just make it less gas efficient.
    // Additionally, we noticed that converting this to memory-safe assembly had no affect on optimizations of other
    // contracts (as it always gets compiled alone in its own compilation unit anyway).
    /* solhint-disable no-inline-assembly */
    assembly {
        let _singleton := sload(0)
        // 0xa619486e == bytes4(keccak256(&quot;masterCopy()&quot;)). The value is right padded to 32-bytes with 0s
        if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) {
            // We mask the singleton address when handling the `masterCopy()` call to ensure that it is correctly
            // ABI-encoded. We do this by shifting the address left by 96 bits (or 12 bytes) and then storing it in
            // memory with a 12 byte offset from where the return data starts. Note that we **intentionally** only
            // do this for the `masterCopy()` call, since the EVM `DELEGATECALL` opcode ignores the most-significant
            // 12 bytes from the address, so we do not need to make sure the top bytes are cleared when proxying
            // calls to the `singleton`. This saves us a tiny amount of gas per proxied call.
            mstore(0x0c, shl(96, _singleton))
            return(0, 0x20)
        }
        calldatacopy(0, 0, calldatasize())
        let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
        returndatacopy(0, 0, returndatasize())
        if iszero(success) {
            revert(0, returndatasize())
        }
        return(0, returndatasize())
    }
    /* solhint-enable no-inline-assembly */
}
"><code><span class="hljs-comment">/// @dev Fallback function forwards all transactions and returns all received return data.</span>
<span class="hljs-function"><span class="hljs-keyword">fallback</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
    <span class="hljs-comment">// Note that this assembly block is **intentionally** not marked as memory-safe. First of all, it isn't memory</span>
    <span class="hljs-comment">// safe to begin with, and turning this into memory-safe assembly would just make it less gas efficient.</span>
    <span class="hljs-comment">// Additionally, we noticed that converting this to memory-safe assembly had no affect on optimizations of other</span>
    <span class="hljs-comment">// contracts (as it always gets compiled alone in its own compilation unit anyway).</span>
    <span class="hljs-comment">/* solhint-disable no-inline-assembly */</span>
    <span class="hljs-keyword">assembly</span> {
        <span class="hljs-keyword">let</span> _singleton <span class="hljs-operator">:=</span> <span class="hljs-built_in">sload</span>(<span class="hljs-number">0</span>)
        <span class="hljs-comment">// 0xa619486e == bytes4(keccak256("masterCopy()")). The value is right padded to 32-bytes with 0s</span>
        <span class="hljs-keyword">if</span> <span class="hljs-built_in">eq</span>(<span class="hljs-built_in">calldataload</span>(<span class="hljs-number">0</span>), <span class="hljs-number">0xa619486e00000000000000000000000000000000000000000000000000000000</span>) {
            <span class="hljs-comment">// We mask the singleton address when handling the `masterCopy()` call to ensure that it is correctly</span>
            <span class="hljs-comment">// ABI-encoded. We do this by shifting the address left by 96 bits (or 12 bytes) and then storing it in</span>
            <span class="hljs-comment">// memory with a 12 byte offset from where the return data starts. Note that we **intentionally** only</span>
            <span class="hljs-comment">// do this for the `masterCopy()` call, since the EVM `DELEGATECALL` opcode ignores the most-significant</span>
            <span class="hljs-comment">// 12 bytes from the address, so we do not need to make sure the top bytes are cleared when proxying</span>
            <span class="hljs-comment">// calls to the `singleton`. This saves us a tiny amount of gas per proxied call.</span>
            <span class="hljs-built_in">mstore</span>(<span class="hljs-number">0x0c</span>, <span class="hljs-built_in">shl</span>(<span class="hljs-number">96</span>, _singleton))
            <span class="hljs-keyword">return</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0x20</span>)
        }
        <span class="hljs-built_in">calldatacopy</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-built_in">calldatasize</span>())
        <span class="hljs-keyword">let</span> success <span class="hljs-operator">:=</span> <span class="hljs-built_in">delegatecall</span>(<span class="hljs-built_in">gas</span>(), _singleton, <span class="hljs-number">0</span>, <span class="hljs-built_in">calldatasize</span>(), <span class="hljs-number">0</span>, <span class="hljs-number">0</span>)
        <span class="hljs-built_in">returndatacopy</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-built_in">returndatasize</span>())
        <span class="hljs-keyword">if</span> <span class="hljs-built_in">iszero</span>(success) {
            <span class="hljs-keyword">revert</span>(<span class="hljs-number">0</span>, <span class="hljs-built_in">returndatasize</span>())
        }
        <span class="hljs-keyword">return</span>(<span class="hljs-number">0</span>, <span class="hljs-built_in">returndatasize</span>())
    }
    <span class="hljs-comment">/* solhint-enable no-inline-assembly */</span>
}
</code></pre><p>This code is written in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.soliditylang.org/en/latest/yul.html">YUL</a>, an EVM assembly language. It’s a bit harder to read than plain Solidity, but we’ll break it down and explain it line by line.</p><ol><li><p>First, on line 40, the code loads storage slot 0, where the address of the singleton is stored. We will skip the optimisation.</p></li><li><p>On line 52, it copies the calldata from the transaction into memory slot 0.</p></li><li><p>Then, on line 53, it calls the contract pointed to by the singleton address, using the copied calldata in memory slot 0. It’s a delegate call, so any storage changes occur in this contract.</p></li><li><p>On line 54, the returned data from the delegatecall() is copied back into memory slot 0.</p></li><li><p>Finally, on line 56, the transaction is reverted if the call didn’t succeed.</p></li></ol><p>Understand this, the contract address stored in the singleton variable on storage slot 0 is crucial. It typically stores the address of the latest implementation from Gnosis Safe, and can be updated to a newer version.</p><p>However, if it’s changed to a malicious contract, anything could happen to the safe wallet, including the loss of all assets protected by the smart contract. Unfortunately, this is exactly what the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.lrqa.com/en-sg/cyber-labs/the-1.5bn-bybit-theft-what-happened/">Bybit team signed</a>. A harmful update to the singleton variable, pointing it to a malicious drainer contract.</p><p>That’s all for today! In the next article, we’ll explore how Safe wallets manage multiple owners and how we can retrieve the owner list directly on-chain.</p>]]></content:encoded>
            <author>cizeon@newsletter.paragraph.com (Cizeon)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/0c233bbe7d6d061c6b7bb58b99ffa69c0914ba3e08828f7de92c70032d295bc0.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>