![](https://miro.medium.com/max/1060/1*DZopze1Xtir7mZHraVNZ_w.png)
### Smart Contract Deep Dive
By Jefferson Tang
---
![](https://i.ibb.co/7jqWcHn/Screen-Shot-2022-02-21-at-5-03-31-pm.png)
---
### What is a multi-signature wallet?
---
![](https://coincodecap.com/wp-content/uploads/2021/03/image-18.jpeg.webp)
---
![](https://miro.medium.com/max/1400/1*OymI4OIm23KYTXwrUwJFeA.png)
---
### Why a multi-signature wallet?
- Single-signature wallet vulnerable if single private key compromised
- For individual - require multiple devices to sign transactions. Can lose one private key.
- For teams - require multiple team members to sign every transaction. Helps to prevent unauthorized access to the company/DAO wallet.
---
A Gnosis Safe account takes just 60 seconds to set up
https://twitter.com/econoar/status/1194731123340763136
---
### Gnosis have performed formal verification of their contracts
- Time-intensive method to mathematically verify that a contract works as intended
- https://blog.gnosis.pm/formal-verification-a-journey-deep-into-the-gnosis-safe-smart-contracts-b00daf354a9c
---
### Gnosis Safe Components
- **Gnosis UI**
- **Gnosis Safe Smart Contracts**
- **Safe transaction service:** API to store off-chain signatures
- **Safe Apps:** Enable extra functionality such as governance, fair Auctions
---
### 'Bread & Butter' Call Flow
```sequence
ProxyFactory->Proxy: createProxy()
Proxy->GnosisSafe: setup()
Proxy-->GnosisSafe: execTransaction()
```
---
### ProxyFactory.sol
- Provides a simple way to create a new proxy contract pointing to a mastercopy, and executing a function in the newly deployed proxy all in one transaction.
- The additional transaction is generally used to execute the setup function to initialise the Proxy contract state
---
### EIP1167 - Minimal Proxy Contract
```graphviz
digraph {
ProxyFactory -> Proxy1;
ProxyFactory -> Proxy2;
ProxyFactory -> Proxy3;
ProxyFactory -> "Proxy ...";
Proxy1 -> GnosisSafe;
Proxy2 -> GnosisSafe;
Proxy3 -> GnosisSafe;
"Proxy ..." -> GnosisSafe;
}
```
- Proxies delegatecall to GnosisSafe
- GnosisSafe is the 'mastercopy' or logic contract
---
### Minimal Proxy Contract rationale
- Contract creation is expensive
- Enable cheap deployment of clone 'minimal proxy contracts'
- Each proxy points to a single 'mastercopy' (or singleton, logic contract, implementation contract)
- Also allows upgrading contract logic by reploying and updating address of mastercopy
---
### Proxy.sol
- Only has a constructor and a fallback function!
- Fallback function will delegatecall to GnosisSafe.sol for logic execution
- Storage state (i.e. token balances) maintained in Proxy
---
### GnosisSafe.sol
- setup() - Set up initial Gnosis Safe state
- execTransaction() - execute transactions
---
### GnosisSafe.sol inheritance
```graphviz
digraph {
OwnerManager -> GnosisSafe;
ModuleManager -> GnosisSafe;
FallbackManager -> GnosisSafe;
"..." -> GnosisSafe;
}
```
---
### GnosisSafe.setup()
- OwnerManager.setupOwners()
- FallbackManager.internalSetFallbackHandler()
- ModuleManager.setupModules()
- handlePayment()
---
### OwnerManager.sol
- Add, remove and replace owners
- View list of owners
- View and change threshold number of owners required to confirm a transaction
---
### GnosisSafe.setup() -> OwnerManager.setupOwners()
- Interesting linked list data structure for storing addresses `mapping(address => address) internal owners;`
- If you have address a, b and c as owners, stored as
```solidity=
owners[address(0x1)] = a
owners[a] = b
owners[b] = c
owners[c] = address(0x1)
```
- If owners[z] = 0, z is not a current owner
---
### FallbackManager.sol
- Handle fallback functions
- Required to make Safe 100% compatible with the ERC721 token standard
---
### GnosisSafe.setup() -> FallbackManager.internalSetFallbackHandler()
```solidity=
// keccak256("fallback_manager.handler.address")
bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5;
function internalSetFallbackHandler(address handler) internal {
bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, handler)
}
}
```
---
### ModuleManager.sol
- Modules are smart contracts which add additional functionaly to the GnosisSafe contracts, while separating module logic from the Safe's core contract
- A basic safe does not require any modules
- Adding and removing a module requires confirmation from all owners
- Modules can include daily spending allowances, amounts that can be spent without approval of other owners, etc. Modules enable developers to include their own features via a separate smart contract
---
### GnosisSafe.setup() -> ModuleManager.setupModules()
```solidity=
function setupModules(address to, bytes memory data) internal {
require(modules[SENTINEL_MODULES] == address(0), "GS100");
modules[SENTINEL_MODULES] = SENTINEL_MODULES;
if (to != address(0))
// Setup has to complete successfully or transaction fails.
require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "GS000");
}
```
---
### GnosisSafe.setup() -> ModuleManager.setupModules() -> Executor.execute()
- Makes a specified delegatecall
---
### GnosisSafe.setup() -> GnosisSafe.handlePayment()
- Optional - handle payment of ETH or ERC20 token to a specified address
- Relayer can send the transaction (and pays the ETH gas fee), and will receive ETH or ERC20 token from the Safe
- Enable user to pay for transaction using ERC20 token
---
### GnosisSafe.execTransaction()
- GnosisSafe.encodeTransactionData()
- GnosisSafe.checkSignatures()
- GuardManager.getGuard()
- GuardManager.checkTransaction()
- Executor.execute()
- GnosisSafe.handlePayment()
- Guard.checkAfterExecution()
---
### GnosisSafe.execTransaction() -> GnosisSafe.encodeTransactionData()
- Gets EIP712-compliant hash to be signed by multisig wallet owners
---
### GnosisSafe.execTransaction() -> GnosisSafe.checkSignatures() -> GnosisSafe.checkNSignatures()
- Checks whether signatures provided are valid for provided transaction parameters and owners
- Store multiple 65-byte Ethereum signatures in 'bytes memory signatures'
- Ethereum signature format - {bytes32 r}{bytes32 s}{uint8 v} - Gnosis use different 'v' variable to mark different signature type
- v = 27 or v = 28 in Ethereum ECDSA signature standard
- v = 0 is contract signature, v = 1 is approved hash, v > 30 then adjusted for eth_sign flow
---
### GnosisSafe.execTransaction() -> GuardManager.checkTransaction()
- Could not find official deployed implementation on Github repo
- Seems intended to perform sanity checks and enforce restrictions on transaction parameters
---
### GnosisSafe.execTransaction() features
- Use block-scoping to limit variable lifetime and prevent 'stack too deep' error
---
### GnosisSafe.execTransaction() -> GuardManager.checkAfterExecution()
- Appears to guard against replay attack (nonce tracking) and re-entrancy
---
```sequence
ProxyFactory->Proxy: createProxy()
Proxy->GnosisSafe: setup()
Proxy-->GnosisSafe: execTransaction()
```
---
### Takeaways p1
- Lots of assembly use - very gas optimized
- Using a mapping & uint256 to store owner addresses as a linked list
- O(1) addition and removal of addresses (vs O(n) worst case in dynamic array).
- If owner[address] = address(0), then address is not an owner. O(1) to check if address is an owner.
- O(n) to iterate over list of owners, same as dynamic array. View functions are free in Solidity.
- GnosisSafe functionality neatly segmented into modules, which are each inherited by GnosisSafe.sol
---
### Takeaways p2
- Require error strings - numbered error codes
- Bitwise-and with bitwise mask 0xff..ff, retrieve data that is stored in data chunks smaller than standard 32-byte Solidity word
- Use block-scoping within function body to limit variable lifetime and prevent 'stack too deep' error
---
### Resources p1
- https://github.com/gnosis/safe-contracts
- https://safe-docs.dev.gnosisdev.com/safe/docs/contracts_architecture/
- https://slideslive.com/38911778/gnosis-safe-make-dealing-with-crypto-a-less-scary-thing
- https://medium.com/gauntlet-networks/multisig-transactions-with-gnosis-safe-f5dbe67c1c2d
---
### Resource p2
- https://medium.com/coinmonks/diving-into-smart-contracts-minimal-proxy-eip-1167-3c4e7f1a41b8
- https://coincodecap.com/multi-signature-wallet
- https://www.evm.codes/
---
### Appendix p1 - Code snippet to see variables set in assembly
```solidity=
function a() public view returns (bytes32) {
assembly {
let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)
mstore(0x0, _singleton)
return(0x0, 0x20)
}
}
```
---
### Appendix p2 - ProxyFactory.sol createProxy()
```solidity=
assembly {
// call opcode => create new subcontext and execute code of the given account, then reusme the current one
// returns 0 if the subcontext reverted, 1 otherwise
// mload(data) => return size in bytes of 'data', first 32-byte word will be length of dynamic array allocated in memory
// add(data, 0x20) => skip past first 32-byte word (contains length in bytes of data), to get memory location to start reading 'data' variable
// => essentially loads 'data' argument into calldata to call newly constructed Proxy contract
if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) {
revert(0, 0)
}
}
...}
```
---
### Appendix p3 - Proxy.sol fallback()
```solidity=
assembly {...
// Create new stack slot reserved for _singleton variable
// Assign 32-byte word with address singleton (left-padded with 0s)
// sload(0) => Load storage variable at slot 0 (address variable)
// and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) => Bitwise and, with bitmask of 0x11...11
let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)
...}
```
---
### Appendix p4 - Proxy.sol fallback()
```solidity=
assembly {...
// 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s
// If send transaction with calldata = 0xa619486e ( function selector for masterCopy() )
// Return address singleton
if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) {
mstore(0, _singleton)
return(0, 0x20)
}
...}
```
---
### Appendix p5 - Proxy.sol fallback()
```solidity=
assembly {...
// Copy calldata into memory
calldatacopy(0, 0, calldatasize())
// Attempt delegatecall with tx data given
// Returns 0 if subcontext reverted, returns 1 otherwise
let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
...}
```
---
### Appendix p6 - Proxy.sol fallback()
```solidity=
assembly {...
// Take return data from delegatecall, and copy to memory
returndatacopy(0, 0, returndatasize())
// If delegatecall reverted in subcontext (GnosisSafe.sol, success == 0), revert but provide returndata
if eq(success, 0) {
revert(0, returndatasize())
}
// If delegate call did not revert, return with returndata
return(0, returndatasize())
...}
```
---
### Appendix p7 - SignatureDecoder.signatureSplit()
```solidity=
// The signature format is a compact form of:
// {bytes32 r}{bytes32 s}{uint8 v}
// Compact means, uint8 is not padded to 32 bytes.
assembly {
// 0x41 = 65, signature stored in 65-bytes
// So signature[0] starts at 'signature + 0' in memory
// signature[2] starts at 'signature + 2*65 bytes' in memory
let signaturePos := mul(0x41, pos)
// Add 32-bytes to where signature[i] starts, first 32-bytes contains size
r := mload(add(signatures, add(signaturePos, 0x20)))
// Add 64-bytes to where signature[i] starts
s := mload(add(signatures, add(signaturePos, 0x40)))
// Get 32-byte word starting from 'signature[i] + 65-bytes.
// Bitwise and with bitmask 0xff to grab the first two bytes.
// Returned to caller as uint8-cast of those first two bytes
// Do this because no mload8 in Solidity parser
v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff)
...}
```
---
{"metaMigratedAt":"2023-06-16T19:57:02.396Z","metaMigratedFrom":"YAML","title":"Gnosis Multisig Safe","breaks":true,"description":"Presentation on Gnosis multisig wallet","slideOptions":"{\"theme\":\"white\",\"transition\":\"fade\"}","contributors":"[{\"id\":\"5bd646a2-a277-4837-9de1-ee14aa493235\",\"add\":15619,\"del\":5368}]"}