Algo Ssstablecoinsss

AI First Flight #2
Beginner FriendlyDeFi
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Ownership Transfer Does Not Revoke Deployer's Minter Role, Leaving Unlimited Minting Power

Root + Impact

Description

  • Normal Behavior:
    The decentralized_stable_coin.vy contract exports ow.transfer_ownership (line 40), which only transfers the owner role. During deployment, the ERC-20 initialization sets the deployer as both owner and minter.


  • Specific Issue:
    The deployment script (deploy_dsc_engine.py:25) first grants the engine minter permission, then transfers ownership to the engine. Because ow.transfer_ownership does not revoke the old owner's minter role, the original deployer retains unlimited minting power over DSC. The engine contract has no function to revoke this permission either.

# decentralized_stable_coin.vy:40
exports: (
erc20.IERC20,
erc20.IERC20Detailed,
erc20.burn_from,
erc20.mint,
erc20.set_minter,
ow.owner,
ow.transfer_ownership, # @> only transfers owner, not minter
)

Risk

Likelihood:

  • The original deployer can mint unlimited DSC at any time, even after ownership is transferred.

  • This undermines the entire stablecoin system and can drain collateral from the engine.

  • No one can revoke the deployer's minter role because the engine has no such function.

Impact:

  • Impact 1

  • Impact 2

Proof of Concept

The test deploys DSC, grants the engine minter permission, and transfers ownership to the engine. After the transfer, dsc.owner() returns the engine, but dsc.is_minter(deployer) still returns True. The test then proves the deployer can mint 1,000,000 DSC out of thin air, even though they are no longer the owner. This confirms the vulnerability.

import boa
from eth_utils import to_wei
​
from src import decentralized_stable_coin, dsc_engine
from src.mocks import MockV3Aggregator, mock_token
​
​
def test_old_owner_retains_minter_after_transfer():
dsc = decentralized_stable_coin.deploy()
weth = mock_token.deploy()
eth_usd = MockV3Aggregator.deploy(8, 100_000 * 10**8)
engine = dsc_engine.deploy(
[weth.address, weth.address],
[eth_usd.address, eth_usd.address],
dsc.address,
)
​
deployer = boa.env.eoa
​
# 1. Deployer is the initial owner and minter
assert dsc.owner() == deployer
assert dsc.is_minter(deployer)
​
# 2. Deploy script: grant engine minter, then transfer ownership
dsc.set_minter(engine.address, True)
dsc.transfer_ownership(engine.address)
​
# 3. Ownership is transferred
assert dsc.owner() == engine.address
​
# 4. BUG: deployer still has minter role
assert dsc.is_minter(deployer), "Deployer still has minter role after transfer!"
​
# 5. Prove deployer can still mint unlimited DSC
with boa.env.prank(deployer):
dsc.mint(deployer, to_wei(1_000_000, "ether"))
assert dsc.balanceOf(deployer) == to_wei(1_000_000, "ether")
​
print("Deployer minted 1,000,000 DSC after ownership transfer")

Recommended Mitigation

Fix 1: Export erc20.transfer_ownership instead of ow.transfer_ownership.

Fix 2: provides a manual alternative.

Fix 3: ensures the vulnerability is caught by the test suite.

#fix1
exports: (
erc20.IERC20,
erc20.IERC20Detailed,
erc20.burn_from,
erc20.mint,
erc20.set_minter,
ow.owner,
- ow.transfer_ownership,
+ erc20.transfer_ownership,
)
​
#fix2
dsc.set_minter(dsc_engine_contract.address, True)
+ dsc.set_minter(deployer, False)
dsc.transfer_ownership(dsc_engine_contract.address)
​
#fix3
+ def test_old_owner_cannot_mint_after_transfer():
+ # ... deploy and transfer ownership ...
+ with boa.reverts():
+ with boa.env.prank(deployer):
+ dsc.mint(deployer, to_wei(1, "ether"))
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 1 hour ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!