If the ProxyFactory::distributeByOwner() is executed before the Proxy contract has been deployed, the transaction will be executed successfully, but the stuck tokens will not be transferred to a rescue requestor.
The distributeByOwner() is used for recovering the rescue requestor's stuck tokens after a contest expires. The function will trigger the _distribute() to execute the contest's Proxy contract.
The transaction will not be reverted as expected if the Proxy has not been deployed before calling the distributeByOwner(). In other words, the transaction will be executed successfully, but the stuck tokens will not be transferred to the rescue requestor.
The root cause is that the _distribute() will make a low-level call to the Proxy without checking the existence of its contract.
The distributeByOwner() triggers the _distribute(): https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/ProxyFactory.sol#L217
The _distribute() makes a low-level call without checking the existence of the Proxy's contract: https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/ProxyFactory.sol#L250
The event Distributed will be emitted regardless of whether the Proxy has a contract: https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/ProxyFactory.sol#L252
The rescue transaction will be executed successfully, but the stuck tokens will not be transferred to the rescue requestor. This incident can cause off-chain services to malfunction and confuse the owner (protocol admin) and the rescue requestor.
Manual Review
Verify that the target Proxy's address has a contract bytecode before executing the Proxy in the _distribute(), as below.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.