On Efficient Ethereum Transactions

& Introducing HomeWork 🏠🛠️

0age
0age
Jun 24 · 10 min read
UNLIMITED…. POWER!!!

tl;dr — You’re not interacting with smart contracts to the fullest of your potential. Using HomeWork, you can craft transactions that use a wallet contract with no permanent code to combine multiple operations into one and, more generally, manage your on-chain activity like a pro. Spoiler alert: it uses metamorphic contracts, of course!


In the beginning, Satoshi said: let there be transactions. By proving knowledge of a secret string of ones and zeroes, and submitting that proof for all to see and record without exposing that secret, the submitter of a transaction, and only that submitter, could impart a particular state change on a distributed ledger. And it was good.

Then, Szabo & Buterin said: let there be smart contracts. By placing a public string of ones and zeroes at an account address for all to see and record, and by allowing other accounts to call into it and impart state changes based on the logic and state contained there, a new universe of possibilities could unfold. And it was also good.

But a great divide still exists between these two independent domains. Transactions cannot take advantage of many resources available to smart contracts, such as combining multiple calls, handling failures, employing conditional logic, utilizing callbacks, and persisting state. On the other hand, contracts lack the flexibility to adapt to changing conditions and so must wage constant battle against potential exploits, and must be abandoned if they fall into obsolescence.

Contracts are built like fortresses, designed to withstand constant onslaught. Externally-owned accounts, on the other hand, are simply nomads wandering the digital desert. To bridge this gap, transaction submitters need a persistent base of operations, one that unlocks the capabilities of contracts but with the lightweight flexibility of transactions. In other words, they need a home.

All we need is a humble little spot on the blockchain to call our own.

Flexible Contracts

One of the key features of smart contracts is their permanence, often referred to as immutability. In order to reliably interact with a contract, you have to be able to rely on the contents of that contract. This property is important enough that it is encoded directly into the DNA of contract deployment itself.

The original design of contract deployment in the Ethereum Virtual Machine enforces this invariant as follows: the deployment address of each contract is derived by combining the account address of the deployer with a nonce that is incremented every time a successful deployment takes place (or, in the case of externally-owned, every time any transaction takes place). This ensures that code can only be deployed once to a given contract address, and immutability is preserved.

An immutable smart contract, standing firm and protecting the city below.

However, it also makes it very difficult to efficiently designate an address ahead of time, before a contract has actually been deployed there. Use-cases like state channels are facilitated via counterfactual instantiation, where contracts are only deployed when needed. To support these and other applications, a new deployment method (CREATE2) was added to the EVM that does away with account determination via the nonce of the deployer, and instead uses the contract’s creation code and an adjustable salt value to derive the deployment address.

Taken on its face, this deployment method appears to preserve the guarantees of contract permanence. If the contract’s creation code is altered, the deployment address will also change. But there is now a chink in the armor of immutability — a contract’s creation code may be non-deterministic, meaning that its execution may differ based on external factors at the time of deployment even though its creation code remains the same.

Furthermore, should this contract SELFDESTRUCT, it can then be redeployed to the same address with new bytecode. Contracts that exhibit this behavior are called metamorphic contracts, and they soundly reject the standard tenets of contract immutability, though they can easily transition back into an immutable state at any point should their contract code lack a means to SELFDESTRUCT or their means of deployment be made unavailable. These types of contracts provide us with the means to craft more efficient transactions from a persistent account.

Thus, the methodical deconstruction of your precious contract immutability begins.

Enhanced Transactions

Vanilla transactions are quite limited in what they can accomplish on their own. They can send ether and data to other accounts or deploy new contracts. When calling into another account, that data is almost always just composed of a function selector and some arguments — it does not, and indeed cannot, specify actual opcodes to run. Instead, it is up to the receiving account to take in those arguments and process them.

This paradigm limits transactions to interacting with one account at a time. It also precludes the addition of more advanced logic or conditional behavior that is not already contained at the account being called. Basically, you cannot include your own custom code to run when making a transaction, and that’s quite a bummer. (Interestingly, transaction scripts that let you do this are a proposed feature of Libra / Zuck-Buck’s Move language.)

But what if that data could be executed as code? In effect, a transaction could be constructed that behaves like an advanced transaction script, calling into multiple different accounts and incorporating custom logic like handling exceptions or grouping calls into one atomic operation. By deploying a contract to a metamorphic address that executes the supplied code and then deletes itself, we can create a wallet contract with a permanent address, but no permanent code. This, among other use-cases, is what HomeWork was built to enable.

Alternate Article Title: On Efficient Ethereum Emoji.

HomeWork is an autonomous utility that can be used to find a home address and to deploy (and re-deploy) contracts with arbitrary bytecode to the account at that address. Each home address has a corresponding key, with a specific controller, that it uses to derive the deployment address. Furthermore, HomeWork will allow the controller of a key to mint an ERC721 token for its corresponding home address. Then, the owner of that NFT can redeem it in order to gain control over deployment to that address.

There are plenty of interesting applications of HomeWork that don’t involve efficient transactions or metamorphism at all — for instance, you can easily deploy code that refers to its own address as a constant, or find an efficient contract address that will save you gas. (One good example is explained in EIP1167: if you are deploying a bunch of minimal proxies that refer to a contract with leading zero bytes, you’ll be able to save 200 gas per zero byte on each deployment.) Basically, you can totally decouple address derivation from deployment.

However, the real game-changer is what you can do with home addresses that are metamorphic. By way of example, consider this common UX headache: many common ERC20 operations require two transactions, one to approve another account to transfer tokens on your behalf, and one to execute the actual transfer from that account. Let’s assume you are using the following contract to swap two tokens (for simplicity’s sake, it’ll just support a 1:1 swap):

// example token swapper contract.
contract TokenSwapper {
ERC20 tokenA; // gives this token
ERC20 tokenB; // takes this token

constructor(ERC20 tokenToSwap) public {
// create the token to give
// (mints this contract an initial balance)
tokenA = new ERC20();
// assign the token to take
tokenB = tokenToSwap;
}

// swaps one of token A for one of token B.
function swap() public returns (bool) {
// take one of token B from the caller (NEEDS APPROVAL)
tokenB.transferFrom(msg.sender, address(this), 1);

// give the caller one of token A in return.
tokenA.transfer(msg.sender, 1);

return true;
}
}

Using a home address as a wallet contract and keeping your tokens there, this becomes a one-transaction operation that can be achieved by deploying a contract like this:

interface ITokenSwapper {
function swap() external returns (bool);
}
interface IERC20 {
function approve(address spender, uint256 amount)
external returns (bool);
}
// deploy this "transaction" contract to the home address.
contract ApproveAndTransferFrom {
constructor(
ITokenSwapper tokenSwapper,
IERC20 tokenToSwap
) public {
// approve the swapper to transfer for you.
require(tokenToSwap.approve(address(tokenSwapper), 1));
// make the swap.
require(tokenSwapper.swap());
// clean up the code here so we can use it again...
// and DO NOT set address(this) as the recipient!!
selfdestruct(tx.origin);
}
}
Knight to C8, Bishop to E4, King to A6 in one move. Checkmate, skeptics.

So, if you’re using a home address as a wallet contract, there are a ton of interesting things you can do in a very flexible way by simply deploying a contract, performing some calls or whatever, and destroying it. You also don’t have to destroy the contract right away — as a matter of fact, it’s more efficient to leave the code in place for a while if you’ll be reusing it so you don’t incur the overhead of deploying it each time (just be sure that it can be destroyed if you want to change it down the line). And if SELFDESTRUCT makes you feel all squeamish, there’s also a brilliant new multisig called exeDAO that uses a similar trick to HomeWork and allows for executing near-arbitrary bytecode on a wallet contract without wiping state between transactions.

The Nuts And Bolts

There are quite a few distinct ways to craft non-deterministic contact creation code, but the technique that HomeWork uses is the metamorphic delegator pattern. First, we deploy a contract that stores the contract creation code of the contract we want to deploy as runtime code. Then, we can deploy a contract with non-deterministic creation code to a known address that will DELEGATECALL into that runtime storage contract. In doing so, the initialization logic will be executed in the context of the caller, and the bytecode returned from a successful DELEGATECALL can then be written to the caller’s runtime (or passed along as the revert message in case of failure).

There are a few reasons this pattern is a good choice for general-purpose deployments to home addresses:

  • Deployed contracts can use a constructor, which is critical in enabling their usage as full-featured wallet contracts and doesn’t require the use of initializer functions.
  • Computing the home address for a given key can be performed in fewer steps than, say, a 2-step deploy through a transient metamorphic contract (i.e. one that deploys the contract with CREATE and then immediately SELFDESTRUCTs), which means that efficient addresses can be found more quickly.
  • The contract creation code used to deploy the metamorphic contract does not need to be deployed every time it’s used, and can optionally be deployed ahead of time (perhaps in a vetted library) and reused.

There are many more potential applications of this technology, including transferrable write access to runtime storage contracts, construction of baskets of assets and fungible accounts, and powerful new ways to onboard new users using managed accounts and meta-transactions, but this is already plenty of new information to digest.

You can probably feel the mental cholesterol seeping from your pores right now.

If you want to start playing around with HomeWork, it’s deployed to 0x0000000000001b84b1cb32787B0D64758d019317 (yes, the contract address has six leading zero bytes) on Mainnet, Ropsten, Rinkeby, Kovan, and Goerli. You can also trade HomeWork NFTs on OpenSea. Feel free to share your thoughts and questions in the HomeWork Discord server or the HomeWork Telegram channel (they’re also a good source for updates on new interfaces, tooling, and other improvements) and to submit issues or PRs to the HomeWork Github repo.

Here Be Dragons

Before you start deploying contracts through HomeWork, take note of a few important caveats:

  • Verification of contracts deployed through HomeWork on block explorers is still a work-in-progress. If you want to let people verify it themselves, point them to the runtime storage contract used to store your contract’s creation code — that should match the bytecode compiled from source. (EDIT: BlockScout now supports contract verification for contracts deployed via HomeWork!)
  • Remember that HomeWork is msg.sender for the deployed contract (rather than the account that’s telling HomeWork to deploy the contract).
  • Be aware that HomeWork NFTs can be redeemed, then locked up again with the same tokenId — this means that if you buy one with additional assets or permissions that it controls, it should first be placed in a wrapper contract that sets a new tokenId for each wrapped HomeWork NFT.
  • As a reminder, if you want to redeploy a contract to a home address, you have to SELFDESTRUCT the old one first. Work is underway on a HomeWork wrapper contract that will provide some additional protections against “de-mutability”. You may want to wait until that’s ready before going all-out on HomeWork-enabled contract transaction scripts.

It goes without saying, but I’ll say it anyway — this tool (as well as the rise of upgradeable contracts as a whole) introduces new risks for everyone to be aware of. It’s more important than ever to do your homework, lest the contracts you interact with summon new monsters from the mutable depths to take vengeance upon you.

Setting sail for new shores… what could possibly go wrong?

Efficient Transactions — The Takeaway

Immutability isn’t all it’s cracked up to be, especially when it comes at the cost of user experience. Not all contracts need to be set in stone. By using temporary, flexible contracts at a fixed home address to enhance transactions, we can restore the balance and enable an entirely new, more efficient mechanism for performing sophisticated interactions with smart contracts on Ethereum.


Big thanks to Dillon Kellar, Matt Czernik, Charles Cooper, Raymond Pulver, Jaime Iglesias, Daniel Viau, Alex Atallah, Santiago Palladino, Connor Spelliscy, Michael Dunworth, and the BlockScout team for all the help with this article and HomeWork in general — and last but not least, thank you for sticking with me all the way to the end of yet another technical article!

Coinmonks

Coinmonks is a technology-focused publication embracing decentralize technologies. We are Non-profit and education is our core value. Learn, Build and thrive. Our other project— https://coinmonks.com, https://cryptofi.co, https://coincodecap.com

0age

Written by

0age

EVM Security & Upgradeability Researcher

Coinmonks

Coinmonks

Coinmonks is a technology-focused publication embracing decentralize technologies. We are Non-profit and education is our core value. Learn, Build and thrive. Our other project— https://coinmonks.com, https://cryptofi.co, https://coincodecap.com