Coinbase Logo

Exploiting governance with metamorphic proposals

Tl;dr: The Unit 0x team, a Coinbase team dedicated to investigating and preventing attacks in the broader crypto ecosystem, investigated a recent governance takeover which serves as a great case-study for handling metamorphic contracts by governance protocols. In this in-depth analysis we will discuss the “Wild Magic” technique used to trick reviewers of the governance proposals, how it was used to steal assets from the governance contract, and explore attacker’s on-chain activity following the hack. We will also discuss critical lessons learned about handling governance proposals.

By Peter Kacherginsky, Heidi Wilder


, June 2, 2023

, 15min read time

Screenshot 2023-06-02 at 4.53.58 PM

While this compromise does not directly target Coinbase Exchange custodial services or systems, we strongly believe that it is important to address any attacks or vulnerabilities within the crypto community as a whole and hope the information in the blog will help strengthen and inform the entire ecosystem.

We recently witnessed a sophisticated attack on a governance protocol, which resulted in the loss of $3.2m. The attack was initiated with a specially crafted governance proposal that was modified to allow theft of funds just after it was approved.

Smart contract governance actions are performed by executing stand-alone contracts (proposals) which can modify arbitrary settings, set variables, perform upgrades, and execute other critical actions. Running arbitrary code is extremely dangerous as a malicious proposal can completely alter the behavior of a smart contract which may lead to theft of funds or permanent disabling of the protocol. As a result, all proposals are subject to careful analysis during a voting window where malicious code is hopefully caught and not allowed to execute.

In order to trick reviewers and voters, the attacker created a benign contract which closely mimicked a previously approved proposal. However, the malicious proposal used a metamorphic contract technique dubbed “Wild Magic” by Jason Carver in 2019. The technique allows bad actors to add malicious behavior to already deployed contracts. 

The DeFi ecosystem has previously experienced a number of compromises involving governance. For example, on April 18, 2022 Beanstalk lost $181m after an attacker enacted a malicious proposal by obtaining votes from a flashloan. However, unlike previous governance takeovers which involved clearly malicious proposals, this incident is the first known instance of a metamorphic contract used to trick the voting committee. The incident serves as a novel case study into governance exploitation that should benefit all existing projects.

In this comprehensive report we will discuss the inner workings of governance mechanisms and metamorphic contracts as well as exactly how the latter was used to compromise the governance contract. We will also discuss the attacker's actions post-exploitation and critical recommendations when reviewing future proposals.


There are times where it is useful to modify a smart contract’s behavior to update settings, pause the protocol, or even upgrade code. To ensure that no single party can make those changes at will, decentralized protocols often implement a governance mechanism to evaluate potential changes to the protocol.

Token voting is one such form of decentralized governance. Individuals holding the protocol’s governance token have the option to propose and vote on changes to the protocol's contracts and parameters. For many protocols, only a very small number of token holders actively participate in governance.

Governance proposals are typically implemented as stand-alone smart contracts which are executed in the context of the governance contract. This allows proposals to have complete control over all of the storage variables as well the ability to call internal functions which are not normally available to the public.

Metamorphic Contracts The traditional methods to deploy smart contracts on Ethereum blockchain include an EOA sending contract bytecode to the 0x0 address or using a CREATE opcode in a deployer smart contract. Both of these methods create smart contracts at predictable addresses calculated as follows.

new_address = hash(sender, nonce)

As displayed above, the contract address is a hash of both the sender’s address and the nonce of the address at a particular point in time. The nonce is a counter of the total number of transactions sent by an address which is 0 for new addresses. Since nonce increments with every transaction, the same EOA or smart contract will generate new contract addresses for all future deployments.

What is CREATE2?

CREATE2 opcode was introduced in 2019 with the Constantinople fork to allow contracts to be deployed at predictable addresses, regardless of what point in time or nonce of the deployer. Smart contract addresses can be calculated as follows

new_address = hash(0xFF, sender, salt, hash(bytecode))

As you can see above, a contract’s address with CREATE2 consists of a hash of:

  • A constant 0xff.

  • The deployer address.

  • An arbitrary salt specified by the deployer.

  • Hash of the contract’s bytecode

Notice, the above formula does not include address nonce so identical bytecode can be deployed at the same address regardless of on-chain activity.


Another critical opcode necessary to understand the exploit is SELFDESTRUCT which effectively erases the smart contract and all of its state from the blockchain. This also includes resetting nonce for the deployed address to 0.

Wild Magic

The above opcodes can be combined to effectively redeploy a completely different bytecode creating a metamorphic contract. The operation can be performed in two stages:

Stage 1: Initial Deployment

First let’s perform the initial deployment of a benign contract which we will replace later:

  • Deploy a utility smart contract using CREATE2 address. The utility smart contract implements a single function which accepts additional bytecode as a parameter and deploys it using CREATE opcode.

  • Supply benign bytecode to the utility smart contract which will in turn deploy it on the blockchain.

Stage 2: Replacing the Bytecode

  • Both the utility and benign contracts have a function to SELFDESTRUCT which is called to erase them from the blockchain and reset all state including nonce.

  • Redeploy the utility smart contract using CREATE2. Because the contract’s functionality relies on user supplied parameters its bytecode did not change so it will be deployed at the same address.

  • Supply malicious bytecode to the utility smart contract. Because utility smart contract’s nonce was reset back to 0, the next contract it will deploy will use the same address as before.

The original benign smart contract was not completely replaced by malicious code at the same address. To best illustrate how the above steps work let’s discuss how it was used in a recent sample case study.

The exploit in the wild

The recent attack relied on unsuspecting governance voters and leveraged metamorphic contracts described above in order to deploy a benign initial proposal and later redeploy it with malicious code. It was executed in the following stages:

1. Proposal Deployment

First the attacker 0x592340 deployed a metamorphic contract proposal to the unsuspecting dApp’s Governance using two additional helper contracts. The first contract 0xaf5461 was deployed on 2023-05-13 7:07:35. Upon initialization it created an additional deployer contract which in turn created the final contract used for the governance proposal. Below is a description of each of the created contracts:

Screenshot 2023-06-02 at 5.03.30 PM

The above deployments are illustrated below:

Screenshot 2023-06-02 at 5.04.08 PM

Below is the source code of the the proposal contract deployed at 0xc50389:

contract Proposal is Ownable {
function getNullifiedTotal(address[4] memory relayers) public view returns (uint256) {
uint256 nullifiedTotal;
address _registryAddress = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
for (uint8 x = 0; x < relayers.length; x++) {
nullifiedTotal += IRelayerRegistry(_registryAddress).getRelayerBalance(relayers[x]);
return nullifiedTotal;
function executeProposal() external {
address[4] memory VIOLATING_RELAYERS = [
0xcBD78860218160F4b463612f30806807Fe6E804C, // thornadope.eth
0x94596B6A626392F5D972D6CC4D929a42c2f0008c, // 0xgm777.eth
0x065f2A0eF62878e8951af3c387E4ddC944f1B8F4, // 0xtorn365.eth
0x18F516dD6D5F46b2875Fd822B994081274be2a8b // abc321.eth
address _registryAddress = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
address _stakingAddress = 0x2FC93484614a34f26F7970CBB94615bA109BB4bf;
function emergencyStop() public onlyOwner {

The malicious proposal looks almost exactly like previously approved proposal #16 with an additional emergencyStop() method which can selfdestruct the contract. This method is key to the attack which redeployed the proposal with additional logic.

2. Proposal and voting

Next on May-13-2023 07:22:59 AM the attacker submitted the above proposal with a reference to a previously approved proposal #16 [Boardroom] [tx]. The proposal was intended to penalize cheating operators of the protocol:

Screenshot 2023-06-02 at 5.05.51 PM

The voting period ended on 2023-05-18 07:24:14 UTC. Following the additional 2 day execution delay, the proposal could be enacted starting at 2023-05-20 07:24:14  UTC.


While waiting for the approved proposal to become executable, the attacker used another account 0x092123 to prepare for theft of funds.

On 2023-05-18 8:47:59 the attacker deployed a helper contract 0x03ecf0 to allow them to withdraw all of the governance tokens assigned to all of the accounts in the malicious proposal. 

The contract includes a method with a 4byte 0xc3375ca5 which performs the following specified number of times:

  • Deploy a new getter contract.

  • Transfer 0 governance tokens to the getter contract to initialize token balance entries.

  • Approve governance tokens spending to the governance contract.

Between 2023-05-18 8:49:47 and 2023-05-18 8:53:11 the attacker called this method 4 times [tx] to deploy 100 contracts listed in the indicators section below. Each contract implements a single function which can generically execute a series of contract calls with specified values.

Metamorphic Proposal

At 2023-05-20 7:18:35 UTC, just before the voting ended, the attacker 0x592340 initiated emergencyStop() [tx] in the stage one contract 0xaf5461 which in turn self-destructed stage two deployer 0x7dc861 as well as the stage three proposal 0xc50389 contracts. 

In the next block 17299107 at 2023-05-20 7:18:47 UTC, the attacker triggered a method in the stage one deployer 0xaf5461 with a 4byte 0xce40d339 [tx]. The method accepts parameters with bytecodes that will be deployed as stage 2 deployer and the updated proposal contract.

Since the attacker did not modify the bytecode for the stage two deployer the aforementioned CREATE2 opcode redeployed at the same address 0x7dc861

CREATE2 hash(0xFF, 0xaf5461, salt, hash(same_bytecode))

The stage two deployer in turn used the CREATE opcode to deploy the proposal contract. As we have previously discussed, the CREATE opcode relies on the deployment address which is still 0x7dc861 and the current nonce. Since 0x7dc861 was just redeployed its nonce was also reset to 0 so the next deployed contract would once again end up at 0xc50389

CREATE: hash(0x7dc861, 0) = 0xc50389

Screenshot 2023-06-02 at 5.07.07 PM

The new implementation included the original code in the proposal as well as code to update balances for 101 accounts. Below is a snippet of the updated proposal:

function executeProposal() external {
address[4] memory VIOLATING_RELAYERS = [
0xcBD78860218160F4b463612f30806807Fe6E804C, // thornadope.eth
0x94596B6A626392F5D972D6CC4D929a42c2f0008c, // 0xgm777.eth
0x065f2A0eF62878e8951af3c387E4ddC944f1B8F4, // 0xtorn365.eth
0x18F516dD6D5F46b2875Fd822B994081274be2a8b // abc321.eth
address _registryAddress = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
address _stakingAddress = 0x2FC93484614a34f26F7970CBB94615bA109BB4bf;
lockedBalance[0x1C406ABB1c6a3Bb12447f933b5D4293701b6e9f2] = 10000 ether;
lockedBalance[0xb4d47EE99E132e441Ae3467EB7D70F06d61b10C9] = 10000 ether;
lockedBalance[0x57400EB021F940B258F925c57cD39F240B7366F2] = 10000 ether;
lockedBalance[0xBC78138A49e5BADDBE7a125659A7b4F661D2770A] = 10000 ether;
lockedBalance[0x68458586990E0d48c034E49b783B08444730d44f] = 10000 ether;
lockedBalance[0xbfFefE62Ca8e0BE1734D267767Ad5923c23bBB05] = 200000 ether;

Interestingly, 101st account 0xbfFefE62Ca8e0BE1734D267767Ad5923c23bBB05 was never deployed as a smart contract in the previous stage. This EOA address was used in later stages of the compromise discussed below.

The execution of the above script in the context of the governance contract sets a balance of 10,000 governance tokens for each of the addresses with the exception of the last one which gets 200,000 governance tokens for a total of 1,200,000 governance tokens.

Proposal Execution

The attacker executed the proposal at 2023-05-20 7:25:11 by calling the execute() method on the Governance Staking contract 0x5efda5 which in turn created balances for 101 fictitious accounts in the malicious proposal.


On 2023-05-20 7:34:35 the attacker 0x092123 performed a sample withdrawal by calling a method with a 4byte 0xa23154f8 from the helper contract 0x03ecf0 which interacted with one of the 100 deployed getter contracts to first unlock 4,000 governance tokens from the governance contract and later transfer it back to the attacker.

The attacker repeated the pattern at 2023-05-20 10:22:35 and 2023-05-21 01:15:35 to steal additional 6,000 governance tokens and 473,000 governance tokens respectively [tx] [tx].

A total of 483,000 governance tokens were stolen since the malicious proposal went into effect and made changes to the governance contract.

Although exfiltration of governance tokens may continue, this incident did not allow any funds to be accessed from the immutable smart contracts associated with the project. Because the immutable protocol contracts cannot be upgraded on-chain by anyone, the malicious governance proposal had no direct effect on their operation. Incident Lessons and Recommendations Multiple failure points contributed to the compromise and slow response:

Initial review failure of the proposal itself and why a proposer would use a matryoshka of contracts to deploy a proposal.

There was a window of about 6 hours when the attacker was performing test transactions. No one detected and alerted those impacted to pull liquidity.


Ensure all governance proposals have a verified source code uploaded to Etherscan or equivalent.

Hire auditors to review and assess each new proposal to be voted on.

Do not allow governance proposals containing SELFDESTRUCT opcode.

Do not allow governance proposals deployed using CREATE2 opcode.

Do not allow governance proposals implemented as upgradeable proxies.

Use automatic governance proposal scanning tools such as Uniswap’s Seatbelt.

Where possible make contracts immutable.

Restrict governance actions to minimum required to operate contracts.

Following the incident there was also renewed discussion of removing SELFDESTRUCT opcode to prevent future attacks.


Appendix A: Indicators

Attacker EOAs:

Ethereum: 0x592340957eBC9e4Afb0E9Af221d06fDDDF789de9 Ethereum: 0x092123663804f8801b9b086b03b98d706f77bd59

Attacker Contracts:

Ethereum: 0xaf54612427d97489707332efe0b6290f129dbacb Ethereum: 0x7dc86183274b28e9f1a100a0152dac975361353d Ethereum: 0xc503893b3e3c0c6b909222b45f2a3a259a52752d Ethereum: 0x1fad009ad35689b5a9b91486148f2f32afe31e23 Ethereum: 0x56b52c864b8b6c22de82242f451e62a43276991c

Zero balance accounts in the governance contract:

Ethereum: 0x1C406ABB1c6a3Bb12447f933b5D4293701b6e9f2 Ethereum: 0xb4d47EE99E132e441Ae3467EB7D70F06d61b10C9 Ethereum: 0x57400EB021F940B258F925c57cD39F240B7366F2 Ethereum: 0xbD23c3ed3DB8a2D07C52F7C6700fDf0888f4f730 Ethereum: 0x548Fd6e5239e9Ce96F3B63F9EEeAd8C461609dc5 Ethereum: 0x6dD8C3C6ADD0F403167bF8d2E527A544464744Bb Ethereum: 0xC883Fa52D656eBF2b665f2B0C9DC69018dB19760 Ethereum: 0x1Eb70Cb3c28BE53b287C4E4770F28a3829a57242 Ethereum: 0xcd280F16CE0b25f85f7520a312EB6B9D76a941D4 Ethereum: 0xeFDAad217f73355Afe99bC3Ff60BA9Fa6f4Bf51D Ethereum: 0x130A2AAE6C3B2a8B0403cA6b9F4e28f3Eb59b021 Ethereum: 0xf06A447EB8ebb3Afe4849fC9Ac14eA7f5FBe480a Ethereum: 0xE3339Ee951522E9C8CC28534179aBF26eF6fC390 Ethereum: 0xCDcFE3Fa83e771d3CeF8AcB0Cf494B00A625Baa0 Ethereum: 0x427F31Efe23C994738F79B81054351a35E020300 Ethereum: 0x7b54c6424602d38c586A73237350d74d3bB1f9e3 Ethereum: 0x86ab338d8f95AB08869004Fa438a0608F896cc85 Ethereum: 0xe447c398F643122bAF82d2a28f0AD743Bf03810a Ethereum: 0x2026e53c38c2a6d344B20774CCE003B9c82d8db4 Ethereum: 0xe2e057027215506ba37aC14b1b3F35447BCE9E00 Ethereum: 0x750379A427c2e905dfb93b57Ff32Cc900B982D58 Ethereum: 0xf3aBB7F9DfEC6fA5696674e434c1291BA99D5365 Ethereum: 0xbDf1b3b48BC47bb400Ed3a774dde6A8a8C087B08 Ethereum: 0xbB7AaF0FC95099b624f33d421A02aF2aA9dEB30b Ethereum: 0xD772cA075b0832981F907d78120BB1d2dDeA9c53 Ethereum: 0xB8BC190B76066d6C36aeA266dD35997b371Bf059 Ethereum: 0xd77B507176504c75e6CDcF2F18E9d5efB674C898 Ethereum: 0x59783a2693E00c2e717cdF8F6AABc30F22a2EE25 Ethereum: 0x2099A879c81d842CB41faE11F64C430980A2489C Ethereum: 0xe54e212b1678DD92AbA5C19c571012fD9591f79b Ethereum: 0xe8746D4Ee2E21b1952f2a299A58e26217b5C83B8 Ethereum: 0xf8DD45c936A23BEC510C3F43340E96624DC64E2a Ethereum: 0xEcC13e5879a24878D391728D21908c06c49a0f35 Ethereum: 0x333fA2ea687d00235C9D30Dd8d0A1Ad9be320223 Ethereum: 0x39bC22EB04601d10D882b3e0Ff7BC48939468111 Ethereum: 0x656c14885D5A4d9617A5338e638E9e09F8742F89 Ethereum: 0xE8c82D0EDA5d845eb020b93F28B4192A485ae46F Ethereum: 0x25bC8ce97Ff49A6e4e0FF19576fdCF4930a86470 Ethereum: 0xe2FAD4491D606c8dc2CCE6533BA06286B55E9e59 Ethereum: 0x211Ecb06CCB94F64a199b8c0Ab50da677F0814A1 Ethereum: 0x86f9EF2d46D977dd5756A145697b21A45cd482aA Ethereum: 0xb2149729d926CadF5Fd4F441D2916f32EE1117BD Ethereum: 0xFcA3B56D3fcDDd26A07B4da219D23c821464E413 Ethereum: 0xf938f086deB7BF8E21e87B7F5ca695736FB72662 Ethereum: 0x2aD04ec2618b937B94FAf84DE1b791ea24c421CB Ethereum: 0xddefD8c3a56B6c94aD7C99515426f35EABd6B1eb Ethereum: 0xF2Faef2A542883655d17a7E1A1F45995FFd96EbD Ethereum: 0x1f91DFE1824F686eaE52dC427725b77491BdF1fe* Ethereum: 0xcE3E4F2E58536c62aB884CDf6ede3d540B3Bada4 Ethereum: 0xD587e79Af0c5739E7CE2fbC61d9BD2E93905903D Ethereum: 0xd526Bf6eeD41e08f553E8C81405346cA57e5681F Ethereum: 0xB04B6457468B638F634DE5E29b5e3695219bdD07 Ethereum: 0x2DC89Da10a6fECd06D1cf4cD2e300892bFb330Ad Ethereum: 0x7D98dFAD3299c1b0A64C4491E79479E25161618E Ethereum: 0x8bc8f686fb9ba1b31bc700ddf1244905F490bebE Ethereum: 0xF6B1FB511ced4Db14c6fB811c160703EE7222a9D Ethereum: 0x18Bb987538429C88364a0F06762446F5f676CD82 Ethereum: 0x5A92902142cE0A9b64A63b59E8c45222Da403ADc Ethereum: 0x2a748636E9a02619B4BB517C00b01Bd554100faB Ethereum: 0x732D52E0f3c42e3FC865b0c3D56ad74bbccF012c Ethereum: 0xBaE4F977BAf53c1f4353A94467116227a36E195f Ethereum: 0x01760D5BA7507B35C24dbE0CD33eD20C6Ebc98F2 Ethereum: 0xfc91b2f505d759DdB8765B2Cc87510E5aCDdbAAf Ethereum: 0x90009a669F2e2282C6264fFa371dB25e6E5266a0 Ethereum: 0x1783D6610a6b8E2fF172eAA09c02F347a03679eF Ethereum: 0x53DCF5fF9804f50B395c1105785e22ae854D8F6E Ethereum: 0x7d01a7eD2f35e2232388686274b28812B1c8AF89 Ethereum: 0x81cF4BcF79E85a6827D59013B91aD077c6ce58Fe Ethereum: 0xaa715EBcF8432cf5821f4Aa5E9d1481FA2Ca13B5 Ethereum: 0x9ae18da8Bfb74456DcbBD23eE2F56C35A7231339 Ethereum: 0xBaB434Bd4DFaA4CefA56B0B7C964facaB74caD13 Ethereum: 0x6c4204b3f40dfF763307d8cd681d02e37B55fE08 Ethereum: 0x4134644AdcC12841De3FC895509d82e099b7f0DF Ethereum: 0xCa79e6797953954e0817052293FE3A8710F3583d Ethereum: 0x8A6DE36E0CcEfb692355E523583b99017aadc62F Ethereum: 0xA867B662A05e6ADba6209BEe4EB8e01764f1F27d Ethereum: 0x5003997c5e8b0438fef1e6Bb2ff79D73ed68C717 Ethereum: 0x335a4d0c4AaC5A5ffD644B3b4FA443679eFa88F9 Ethereum: 0x6F07a83384852f22c11D132b91D8c907790911f8 Ethereum: 0x32B5694222A2191142b09d6aB17c3b3f57d4e679 Ethereum: 0xb99f6AACf00EBFBA50519B1A37B1Ff88E0ae3f9c Ethereum: 0x480ACEBA484e7bBB6a57c8c5F035271C5c21014E Ethereum: 0x314F40B5D640876D8c53381c66B36B55D68195cC Ethereum: 0xcBeC349Eb9ac6656393b001EfF786CDE912c50AB Ethereum: 0xBFf9cb6B8BdA67485e17dD67B450A6A49e76F4bF Ethereum: 0xfd85628806878216d93B623B2e647D1f88Cea027 Ethereum: 0x5b929a832690185A150e7648f9b6476487577bd4 Ethereum: 0x28bbAdF5C8CeA27636a9cA11436030337c416400 Ethereum: 0x9170b1c95DAaDe6fd70E640f1F6FB2911Db62468 Ethereum: 0xC73e7c6333683F25B951941759D4b6038eC51DAE Ethereum: 0xd99b4C7372cC245965Bf24A1762d76228201A4b0 Ethereum: 0x67227DDE7BD55B8C2313822b2EaDB46Eda73A4bB Ethereum: 0xd4D9F6f64A5bAF9D263217EB7f5AE1444A956469 Ethereum: 0xCe85fD8b7D965e807f04F51440585Fc610B061a2 Ethereum: 0xd70b6B4De4afa7B0205bB93E46A994C5815fb0B4 Ethereum: 0xb06F844f02695F6cfA0152B12BcfA757B31eB154 Ethereum: 0x1973653486856a0420Fd92a7c5264c3d4D0319B6 Ethereum: 0xA05F1956dC591b815c66e489bf2313F1Ed39dBe9 Ethereum: 0xBC78138A49e5BADDBE7a125659A7b4F661D2770A Ethereum: 0x68458586990E0d48c034E49b783B08444730d44f Ethereum: 0xbfFefE62Ca8e0BE1734D267767Ad5923c23bBB05

Appendix B: Proposal #21 Comparison

The new token proposal from May 21, 2023 updates all of the address in the original proposal #20 and sets them to 0:


Malicious contract decompiled:

Proposal decompiled:

All of the updated storage slots are the same in both instances.

Coinbase logo

Take control of your money. Start your portfolio today and get crypto.

Sign up for a Coinbase account today and see what the world of decentralized finance can do for you.