-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Halve gas used by SafetyChecker #210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,8 +65,7 @@ contract SafetyChecker is ContractResolver { | |
| { | ||
| bool seenJUMP = false; | ||
| bool insideUnreachableCode = false; | ||
| uint256[] memory ops = new uint256[](_bytecode.length); | ||
| uint256 opIndex = 0; | ||
| uint256 ops = 0; | ||
| for (uint256 pc = 0; pc < _bytecode.length; pc++) { | ||
| // current opcode: 0x00...0xff | ||
| uint256 op = uint8(_bytecode[pc]); | ||
|
|
@@ -85,72 +84,73 @@ contract SafetyChecker is ContractResolver { | |
| } | ||
| } else { | ||
| // check that opcode is whitelisted (using the whitelist bit mask) | ||
| uint256 opBit = 2 ** op; | ||
| uint256 opBit = 1 << op; | ||
| if (opcodeWhitelistMask & opBit != opBit) { | ||
| // encountered a non-whitelisted opcode! | ||
| return false; | ||
| } | ||
| // append this opcode to a list of ops | ||
| ops[opIndex] = op; | ||
| // JUMPI | ||
| if (op == 0x57) { | ||
| // We can now reach all JUMPDESTs | ||
| seenJUMP = true; | ||
| // JUMP | ||
| } else if (op == 0x56) { | ||
| // We can now reach all JUMPDESTs | ||
| seenJUMP = true; | ||
| // we are now inside unreachable code until we hit a JUMPDEST! | ||
| insideUnreachableCode = true; | ||
| // STOP or REVERT or INVALID or RETURN (see safety checker docs in wiki for more info) | ||
| } else if (op == 0x00 || op == 0xfd || op == 0xfe || op == 0xf3) { | ||
| // If we can't jump to JUMPDESTs, then all remaining bytecode is unreachable | ||
| if (!seenJUMP) { | ||
| return true; | ||
| } | ||
| // We are now inside unreachable code until we hit a JUMPDEST! | ||
| insideUnreachableCode = true; | ||
| // CALL | ||
| } else if (op == 0xf1) { | ||
| // Minimum 4 total ops: | ||
| // 1. PUSH1 value | ||
| // 2. PUSH20 execution manager address | ||
| // 3. PUSH or DUP gas | ||
| // 4. CALL | ||
|
|
||
| if (opIndex < 3) { | ||
| return false; | ||
| } | ||
| uint256 gasOp = ops[opIndex - 1]; | ||
| uint256 addressOp = ops[opIndex - 2]; | ||
| uint256 valueOp = ops[opIndex - 3]; | ||
| if ( | ||
| gasOp < 0x60 || // PUSHes are 0x60...0x7f | ||
| gasOp > 0x8f || // DUPs are 0x80...0x8f | ||
| addressOp != 0x73 || // address must be set with a PUSH20 | ||
| valueOp != 0x60 // value must be set with a PUSH1 | ||
| ) { | ||
| return false; | ||
| } else { | ||
| uint256 pushedBytes; | ||
| // gas is set with a PUSH## | ||
| if (gasOp >= 0x60 && gasOp <= 0x7f) { | ||
| pushedBytes = gasOp - 0x5f; | ||
| ops <<= 8; | ||
| ops |= op; | ||
| // [0x57, 0x56, 0x00, 0xfd, 0xfe, 0xf3, 0xf1] all have handlers | ||
| if (opBit & 0x600a00000000000000000000000000000000000000c000000000000000000001 != 0) { | ||
| // JUMPI | ||
| if (op == 0x57) { | ||
| // We can now reach all JUMPDESTs | ||
| seenJUMP = true; | ||
| // JUMP | ||
| } else if (op == 0x56) { | ||
| // We can now reach all JUMPDESTs | ||
| seenJUMP = true; | ||
| // we are now inside unreachable code until we hit a JUMPDEST! | ||
| insideUnreachableCode = true; | ||
| // STOP or REVERT or INVALID or RETURN (see safety checker docs in wiki for more info) | ||
| } else if (op == 0x00 || op == 0xfd || op == 0xfe || op == 0xf3) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be cheaper to also do an
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but it's diminishing returns since it's already gated outside. |
||
| // If we can't jump to JUMPDESTs, then all remaining bytecode is unreachable | ||
| if (!seenJUMP) { | ||
| return true; | ||
|
Comment on lines
+110
to
+111
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See |
||
| } | ||
|
|
||
| // 23 is from 1 + PUSH20 + 20 bytes of address + PUSH or DUP gas | ||
| byte callValue = _bytecode[pc - (23 + pushedBytes)]; | ||
|
|
||
| // 21 is from 1 + 19 bytes of address + PUSH or DUP gas | ||
| address callAddress = toAddress(_bytecode, (pc - (21 + pushedBytes))); | ||
|
|
||
| // CALL is made to the execution manager with msg.value of 0 ETH | ||
| if (callAddress != address(resolveExecutionManager()) || callValue != 0 ) { | ||
| // We are now inside unreachable code until we hit a JUMPDEST! | ||
| insideUnreachableCode = true; | ||
| // CALL | ||
| } else if (op == 0xf1) { | ||
| // Minimum 4 total ops: | ||
| // 1. PUSH1 value | ||
| // 2. PUSH20 execution manager address | ||
| // 3. PUSH or DUP gas | ||
| // 4. CALL | ||
|
|
||
| // if opIndex < 3, there would be 0s here, so we don't need the check | ||
| uint256 gasOp = (ops >> 8) & 0xFF; | ||
| uint256 addressOp = (ops >> 16) & 0xFF; | ||
| uint256 valueOp = (ops >> 24) & 0xFF; | ||
| if ( | ||
| gasOp < 0x60 || // PUSHes are 0x60...0x7f | ||
| gasOp > 0x8f || // DUPs are 0x80...0x8f | ||
| addressOp != 0x73 || // address must be set with a PUSH20 | ||
| valueOp != 0x60 // value must be set with a PUSH1 | ||
| ) { | ||
| return false; | ||
| } else { | ||
| uint256 pushedBytes; | ||
| // gas is set with a PUSH## | ||
| if (gasOp >= 0x60 && gasOp <= 0x7f) { | ||
| pushedBytes = gasOp - 0x5f; | ||
| } | ||
|
|
||
| // 23 is from 1 + PUSH20 + 20 bytes of address + PUSH or DUP gas | ||
| byte callValue = _bytecode[pc - (23 + pushedBytes)]; | ||
|
|
||
| // 21 is from 1 + 19 bytes of address + PUSH or DUP gas | ||
| address callAddress = toAddress(_bytecode, (pc - (21 + pushedBytes))); | ||
|
|
||
| // CALL is made to the execution manager with msg.value of 0 ETH | ||
| if (callAddress != address(resolveExecutionManager()) || callValue != 0 ) { | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| opIndex++; | ||
| } | ||
| } | ||
| return true; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now that I think about it, we can just assume that we've seen a
JUMP. I doubt we'll ever have a case where we reach a halting opcode before seeing aJUMP(no need to update this in this PR, but curious about your thoughts)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meh, personally I'd remove this logic. It's safer the other way, never allow something you don't expect.