Solia saysTimelocks queue privileged actions for a mandatory delay (24-72h typical) and rate limits cap how often they fire — together they give users a window to exit before bad upgrades land.
Privileged actions — upgrading an implementation, changing a fee, moving treasury — need friction. A timelock queues the action for a mandatory delay before it can execute; a guardian role can cancel queued actions during the window. Users seeing a malicious queued upgrade have time to exit. Rate limits add a separate guard: at most N privileged calls per time window.
Together they turn "trust the admin" into "verify the admin and have time to respond." The demo queues an action and shows it rejected before the delay elapses, then accepted after, while a rate limiter caps how many calls fit in a window.
Power-ups you unlock
Timelock queues privileged actions for a mandatory delay
Guardian can cancel during the window
Users see queued upgrades and can exit before they land
Rate limits cap how many privileged calls per window
Combine for defense in depth around the admin path
The Reentrancy Reaper attacks — common mistakes
Setting the delay too short to give users time to react
Letting the guardian be the same address as the admin
Skipping events on queue/execute/cancel (no on-chain trail)
No rate limit, so an attacker bursts many actions through the window
Boss battleQueue an action with a 2-day delay, attempt execution after 1 hour (rejected) and after 2 days (accepted), and rate-limit how many actions fit per window.
Example code
<!doctype html><html><head><meta charset="utf-8"></head>
<body style="background:#06040d;color:#e6e0ff;font-family:monospace;padding:20px"><pre id="o"></pre>
<script>
const DELAY = 86400 * 2; // 2 days
const queue = {};
function propose(actionId, now){ queue[actionId] = now + DELAY; return queue[actionId]; }
function execute(actionId, now){
if(!(actionId in queue)) return 'NOT QUEUED';
if(now < queue[actionId]) return 'TOO EARLY (eta ' + (queue[actionId] - now) + 's from now)';
delete queue[actionId];
return 'EXECUTED';
}
const now = 1700000000;
propose('upgradeImpl', now);
const r1 = execute('upgradeImpl', now + 3600); // 1 hour later
const r2 = execute('upgradeImpl', now + DELAY + 1); // after delay
// rate limit
let calls = 0, windowStart = now;
function rateLimit(t, max, windowSec){
if(t - windowStart > windowSec){ windowStart = t; calls = 0; }
if(calls >= max) return false;
calls++; return true;
}
const rl = [];
for(let i = 0; i < 5; i++) rl.push(' call ' + (i+1) + ': ' + (rateLimit(now + i*60, 3, 3600) ? 'allowed' : 'RATE-LIMITED'));
document.getElementById('o').textContent = [
'TIMELOCK (2-day delay):',
' propose upgradeImpl @ now',
' execute after 1 hour → ' + r1,
' execute after 2 days → ' + r2,
'',
'RATE LIMIT (3 calls / hour):',
...rl
].join('\n');
</script></body></html>