Home

Awesome

🔥 Ambire Wallet 🔥

The first DeFi wallet that combines power, security and ease of use, while also being open-source and non-custodial.

Ambire Wallet

Useful links

Ambire contest details

Hello Wardens 👋

We are looking forward to you diving into our code!

Feel free to ask us anything you want, no matter if it's a minor nitpick or a severe issue. We remain available around the clock in the Code4rena Discord, and don't hestitate to tag @Ivo#8114

Good luck and enjoy hunting! 🐛🚫

We hope you're excited about finally seeing a usable and powerful smart wallet on Ethereum!

Contest scope

All the contracts in contracts/, namely Identity.sol, libs/SignatureValidatorV2.sol, libs/BytesLib.sol, IdentityFactory.sol, wallet/QuickAccManager.sol, wallet/Zapper.sol - that's a total of 772 lines.

Architecture

Ambire is a smart wallet. Each user is represented by a smart contract, which is a minimal proxy (EIP 1167) for Identity.sol (example) - we call "account". Many addresses can control each account - we call this "privileges" in the contract and "authorities" in the UI.

The main contract everything is centered around is Identity.sol, which is the actual smart wallet.

Accounts can execute multiple calls in the same on-chain transaction. We call the array of user transactions a "user bundle" - the user signs the hash of this array along with anti-replay data such as nonce, chainID and others. Once it's signed, anyone can execute it by calling Identity(account).execute

The addresses that control an account (privileges) can be EOAs but they can also be smart contracts themselves, thanks to the SmartWallet signature mode in SignatureValidatorV2 which enables EIP 1271 signatures to be used.

To allow more sophisticated authentication schemes without upgradability, we use a very simple relationship: a periphery contract that only deals with the specific authentication scheme can be added to privileges. For example, if a user wants to convert their account to a multisig, they can remove all other privileges and only authorize a single one: a multisig manager contract, that will verify N/M signatures and call Identity(account).executeBySender upon successful verification. This also works for EIP 1271 signatures since Identity.isValidSignature uses SignatureValidatorV2, which supports EIP 1271 itself, so it will propagate the call down to the multisig manager contract.

This very system is used by QuickAccManager, which is a simple 2/2 multisig, that also allows 1/2 transactions but with a timelock. This is used to allow for simple email/password login that can be upgraded by either backing up the second key or by moving to a hardware wallet. For more info on this authentication scheme please read the security model Gist.

There are two ways for a user bundle to get executed:

The actual proxy for each account is deployed counterfactually, when the first user bundle is executed.

Because user bundles are authorized as signed messages, there's no need for hardware wallets to support EIP 1559 directly.

Similar products include Argent, Gnosis Safe and Authereum. The most notable differences is that the Ambire contracts are designed to be as simple as possible, and prefer composability to upgradability and built-in modularity.

Testing and JS libs

The contracts in scope can also be found in this repo: https://github.com/AmbireTech/adex-protocol-eth/tree/identity-v5.2, specifically the identity-v5.2 branch (NOTE: we only care about the contracts in scope!).

The code is frozen for review on commit 742e800c5a6aabe08c59625f3dfd85139223ee63.

In the repo, there are also tests that can be ran, namely test/TestIdentity.js. Other pieces of code you need to know about are js/Bundle.js, responsible for preparing and signing user bundles, and js/IdentityProxyDeploy.js, responsible for deploying the minimal proxies.

NOTE: The UI is currently in private beta, but you can use the factory contract and Identity.sol to experiment on Polygon mainnet.

Design decisions

The contracts are free of inheritance and external dependencies.

There is no code upgradability and no ownership (onlyOwner) or pausability, to ensure immutability. For easier readability, there are no modifiers, while keeping the code DRY.

Storage usage is cut down to the minimum: when bigger data structures need to be saved, we take advantage of the cheap calldata and always pass them in, verifying the hash against a storage slot in the process, for example QuickAccManager uses this for quick accounts.

Smart contract summary

Identity.sol

The core of the Ambire smart wallet. Each user is a minimal proxy with this contract as a base. It contains very few methods, with the most notable being:

There's a few methods that can only be called by the Identity itself, which means the only way to call them is through a call through execute/executeBySender, ensuring it's authorized. Those methods are setAddrPrivilege, tipMiner and tryCatch.

It's only dependency is an internal one, SignatureValidatorV2.

SignatureValidatorV2.sol

Validates signatures in a few modes: EIP 712, EthSign, SmartWallet and Spoof. The first two verify signed messages using ecrecover, the only difference being that EthSign expects the "Ethereum signed message:" prefix. SmartWallet is for ERC 1271 signatures (smart contract signatures), and Spoof is for spoofed signatures that only work when tx.origin == address(1).

IdentityFactory.sol

A simple CREATE2 factory contract designed to deploy minimal proxies for users. The most notable point here is deploySafe, which is a method that protects us from griefing conditions: CREATE2 will fail if a contract has already been deployed, and this method essentially ensures a contract is deployed without failing if it already is.

The use case of this is counterfactual deployment: the proxy of each account will be deployed when the first user bundle is executed, but we don't want to fail the whole bundle in case the contract has already been deployed.

There is a method to drain the contract of ERC20 tokens.

wallet/QuickAccManager.sol

This contract facilitates a 2/2 multisig scheme described in the aforementioned security model document.

It has a set of methods for sending, scheduling and cancelling user bundles:

And two EIP 712 methods: sendTransfer and sendTxns, which only allow 2/2 signatures, but support typed data signatures.

wallet/Zapper.sol

This contract routes trades for users through any Uniswap V2 or V3 compatible router, and facilitates deposit/withdraw to/from Aave.

Unlike regular contracts that use transferFrom, this one relies on the fact that Ambire accounts can execute multiple calls in a single on-chain transaction with user bundles, so it expects that you just transfer-ed the tokens to it beforehand.

Known tradeoffs

NOTE: "bundle"/"user bundle" in this context means array of Identity-level transactions (Identity.Transaction[])

How to run the tests

See https://github.com/AmbireTech/adex-protocol-eth/tree/identity-v5.2#testing

Networks

The contracts will be deployed on Ethereum, Polygon, Fantom, Binance Smart Chain, Avalanche, Arbitrum and other popular EVM chains.

Final notes

if you're excited about building an easy to use, but powerful smart wallet, feel free to reach out at contactus@ambire.com 🔥