The TellerWithMultiAssetSupportPredicateProxy contract inherits from OpenZeppelin's Pausable contract but is missing public pause() and unpause() functions. Because of this, administrators cannot pause the contract during emergency situations (e.g., security incidents or critical bugs). This can result in temporary freezing of user funds for at least 24 hours, as users could continue depositing through the predicate proxy even when the underlying system should be paused, leading to stuck transactions and inability to halt operations during emergencies.
Vulnerability Details
The TellerWithMultiAssetSupportPredicateProxy contract inherits from Pausable but does not implement the necessary public functions to control the pause state:
contractTellerWithMultiAssetSupportPredicateProxyisOwnable, ReentrancyGuard, PredicateClient, Pausable{// there is No public pause() function// there is No public unpause() function
OpenZeppelin's Pausable provides internal _pause() and _unpause() functions, but these are only accessible if the inheriting contract implements public wrapper functions. Without these functions, the contract is effectively unpausable because:
The internal _pause() and _unpause() functions cannot be called externally
The paused() state can never be changed from its initial false value
Manual pause checks in functions will always pass since paused() returns false
The contract does include manual pause checks in its functions:
These checks are ineffective because the pause state can never be set to true without the missing public functions. Suggested additions:
Also consider using the whenNotPaused / whenPaused modifiers on user-entry functions instead of manual if (paused()) checks.
Impact Details
The bug can cause a temporary freezing of funds for at least 24 hours. Example scenario:
1
During an emergency: administrators attempt to pause the system
The predicate proxy cannot be paused due to missing pause functions.
2
Users continue depositing
The predicate proxy remains operational even when the underlying system should be paused.
3
Funds become temporarily frozen
Deposits may fail or get stuck in the predicate proxy, but users can still initiate transactions that do not complete as expected.
4
Recovery time exceeds 24 hours
The team may need to:
Deploy a new predicate proxy with proper pause functionality
Migrate users to the new contract
Handle stuck transactions and refunds
Update all dependent contracts (like DexAggregatorWrapperWithPredicateProxy)
Financial impact:
User deposits could be stuck in the predicate proxy during emergencies
Cross-chain bridging operations may fail but still consume user funds
Emergency response time is significantly delayed due to inability to pause operations
Potential loss of user confidence and reputation damage
Duration: The funds would be frozen for at least 24 hours while the team attempts to resolve the emergency and deploy fixes.
References
TellerWithMultiAssetSupportPredicateProxy.sol
OpenZeppelin Pausable Contract
DexAggregatorWrapperWithPredicateProxy.sol - Shows dependency on the predicate proxy
if (paused()) {
revert TellerWithMultiAssetSupportPredicateProxy__Paused();
}
function pause() external onlyOwner { _pause(); }
function unpause() external onlyOwner { _unpause(); }
function testCannotPausePredicateProxy() public {
// Deploy the contract
TellerWithMultiAssetSupportPredicateProxy proxy = new TellerWithMultiAssetSupportPredicateProxy(
address(this), address(0x1234), "policy"
);
// The contract is not paused by default
assertEq(proxy.paused(), false);
// Try to call pause() - this should fail to compile or revert at runtime
(bool success, ) = address(proxy).call(abi.encodeWithSignature("pause()"));
assertTrue(!success, "pause() should not exist");
// Try to call _pause() - this should also fail
(success, ) = address(proxy).call(abi.encodeWithSignature("_pause()"));
assertTrue(!success, "_pause() should not exist");
// The contract is still not paused
assertEq(proxy.paused(), false);
}