Acala - TheSchnilch's results

Building the liquidity layer of Web3 finance.

General Information

Platform: Code4rena

Start Date: 22/03/2024

Pot Size: $36,500 USDC

Total HM: 7

Participants: 17

Period: 14 days

Judge: Lambda

Id: 323

League: POLKADOT

Acala Network

Findings Distribution

Researcher Performance

Rank: 6/17

Findings: 1

Award: $1,175.02

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: TheSchnilch

Also found by: Aymen0909

Labels

bug
2 (Med Risk)
primary issue
selected for report
sponsor confirmed
sufficient quality report
:robot:_24_group
M-03

Awards

1175.024 USDC - $1,175.02

External Links

Lines of code

https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/earning/src/lib.rs#L188-L196

Vulnerability details

Description

With unbond_instant, a user can unbond a bonded amount directly without having to wait. However, they must pay a fee for this:

let amount = change.change;
let fee = fee_ratio.mul_ceil(amount);
let final_amount = amount.saturating_sub(fee);

let unbalance = T::Currency::withdraw(&who, fee, WithdrawReasons::TRANSFER, ExistenceRequirement::KeepAlive)?;
T::OnUnstakeFee::on_unbalanced(unbalance);

T::OnUnbonded::happened(&(who.clone(), final_amount));

Here, the mistake is that T::OnUnbonded::happened is called with final_amount, meaning without a fee. As a result, a portion of the shares that were added as shares during bonding can no longer be removed. This, in turn, leads to these shares still receiving rewards that other users cannot receive.

If you insert a println! statement into the functions bond and unbond_instant to display the amounts with which OnBonded::happened and OnUnbonded::happened are called, you will see when you execute the following code that not all shares are removed when the bonded amount of a user is removed with unbond_instant:

For bond:

+ println!("change.change: {:?}", change.change);
144: T::OnBonded::happened(&(who.clone(), change.change));
145: Self::deposit_event(Event::Bonded {
146: 	who,
147: 	amount: change.change,
148: });

For unbond_instant:

+ println!("final_amount: {:?}", final_amount);
196: T::OnUnbonded::happened(&(who.clone(), final_amount));
197: Self::deposit_event(Event::InstantUnbonded {
198: 	who,
199: 	amount: final_amount,
200: 	fee,
201: });

Code that can be inserted into the file modules/earning/src/tests.rs to test both functions:

#[test]
fn poc() {
	ExtBuilder::default().build().execute_with(|| {
		assert_ok!(Earning::bond(RuntimeOrigin::signed(ALICE), 100)); //100 tokens are bonded. change.change is 100

		assert_ok!(Earning::unbond_instant(RuntimeOrigin::signed(ALICE), 100)); //All bonded tokens will be unbonded immediately but only 90 shares will be removed because of the fee amount.
	});
}

The code can be started with the command cargo test poc -- --nocapture

So every time a user calls unbond_instant, some shares will remain in the reward system and continue to accumulate rewards that other users who actually bond can no longer get. These shares can then no longer be removed and are stuck. With a maximum fee of 20%, some shares could accumulate over a period of time and these shares would accumulate rewards. The owners of the shares can still claim these rewards. In addition, one share is no longer the same as one underlying token.

Recommendation

For unbond_instant, the final_amount should not be used to remove the shares, but change.change should be used instead.

Assessed type

Other

#0 - c4-pre-sort

2024-04-07T12:29:31Z

DadeKuma marked the issue as primary issue

#1 - c4-pre-sort

2024-04-07T13:46:23Z

DadeKuma marked the issue as sufficient quality report

#2 - DadeKuma

2024-04-07T13:46:25Z

Here, the mistake is that T::OnUnbonded::happened is called with final_amount, meaning without a fee

I think that the report meant with a fee, not without, so these fees are not unbonded.

#3 - c4-sponsor

2024-04-09T04:02:05Z

xlc (sponsor) confirmed

#4 - c4-judge

2024-04-09T16:39:50Z

OpenCoreCH marked the issue as selected for report

#5 - xlc

2024-04-10T03:12:28Z

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter