Created
July 1, 2020 20:03
-
-
Save smartcontracts/7c0c9dc98bbb4392493bc60ce4b0b93f to your computer and use it in GitHub Desktop.
PoC for the constant modification idea
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import BigNumber = require('bn.js') | |
import { DEFAULT_UNSAFE_OPCODES, Opcode, EVMOpcode } from "@eth-optimism/rollup-core"; | |
const UNSAFE_OPCODES = Opcode.ALL_OP_CODES.filter((opcode) => { | |
return ( | |
DEFAULT_UNSAFE_OPCODES.includes(opcode) || | |
Opcode.JUMP_OP_CODES.includes(opcode) || | |
Opcode.isPUSHOpcode(opcode) || | |
opcode === Opcode.CALL | |
) | |
}) | |
const SAFE_OPCODES = Opcode.ALL_OP_CODES.filter((opcode) => { | |
return !UNSAFE_OPCODES.includes(opcode) | |
}) | |
interface SanitizationPair { | |
addendA: EVMOpcode | |
addendB: EVMOpcode | |
} | |
interface SanitizationMapping { | |
[name: string]: SanitizationPair | |
} | |
const findSanitizationPair = (unsafeOpcode: EVMOpcode): SanitizationPair => { | |
const target = new BigNumber(unsafeOpcode.code) | |
for (const opcodeA of SAFE_OPCODES) { | |
const addendA = new BigNumber(opcodeA.code) | |
for (const opcodeB of SAFE_OPCODES) { | |
const addendB = new BigNumber(opcodeB.code) | |
if (addendA.add(addendB).eq(target)) { | |
return { | |
addendA: opcodeA, | |
addendB: opcodeB, | |
} | |
} | |
} | |
} | |
throw new Error('Could not find a valid sanitization pair for the given opcode') | |
} | |
const getOpcodeSanitizationMapping = (): SanitizationMapping => { | |
return UNSAFE_OPCODES.reduce((mapping, opcode) => { | |
mapping[opcode.name] = findSanitizationPair(opcode) | |
return mapping | |
}, {}); | |
} | |
const numberToBuffer = (num: number): Buffer => { | |
return (new BigNumber(num)).toBuffer() | |
} | |
export const sanitizeConstant = (unsafeConstant: Buffer): Buffer => { | |
const sanitizationMapping = getOpcodeSanitizationMapping() | |
const totalWords = Math.ceil(unsafeConstant.length / 32); | |
const totalWordsHex = numberToBuffer(totalWords) | |
let sanitizedConstant: Buffer[] = [] | |
let extraOffset: number | |
if (totalWords === 1) { | |
// Constant isn't greater than 32 bytes, so we'll save it into a single | |
// slot of memory. | |
extraOffset = 0 | |
sanitizedConstant = sanitizedConstant.concat([ | |
// Push the value of the free memory pointer. We'll load this in order to | |
// get a pointer to some free location of memory. | |
Opcode.PUSH1.code, | |
Buffer.from('40', 'hex'), | |
/* | |
Stack: | |
0: 0x40 | |
*/ | |
// Load the free memory pointer. | |
Opcode.MLOAD.code, | |
/* | |
Stack: | |
0: <pointer to free memory> | |
1: 0x40 | |
*/ | |
// Clean up after ourselves. | |
Opcode.SWAP1.code, | |
Opcode.POP.code, | |
/* | |
Stack: | |
0: <pointer to free memory> | |
*/ | |
]) | |
} else { | |
// Constant is greater than 32 bytes, so we'll have to create a dynamic | |
// array instead. | |
extraOffset = 32 | |
sanitizedConstant = sanitizedConstant.concat([ | |
// Push the total number of words in the array. We'll store this into the | |
// first slot of whatever free memory block we're assigned. | |
Opcode.PUSH1.code, | |
totalWordsHex, | |
/* | |
Stack: | |
0: <total number of words> | |
*/ | |
// Get the highest available memory block. | |
Opcode.MSIZE.code, | |
/* | |
Stack: | |
0: <pointer to free memory> | |
1: <total number of words> | |
*/ | |
// Store the total number of words in the first slot. | |
Opcode.MSTORE.code, | |
// Clean up after ourselves. | |
Opcode.SWAP1.code, | |
Opcode.POP.code, | |
/* | |
Stack: | |
0: <pointer to free memory> | |
*/ | |
]) | |
} | |
for (let i = 0; i < totalWords; i++) { | |
const currentByteIndex = i * 32; | |
const currentWord = unsafeConstant.slice(currentByteIndex, currentByteIndex + 32) | |
const addendWordA = currentWord | |
const addendWordB = Buffer.from('00'.repeat(32), 'hex') | |
for (let j = 0; j < 32; j++) { | |
const opcode = Opcode.parseByNumber(currentWord[j]) | |
if (UNSAFE_OPCODES.includes(opcode)) { | |
const sanitizationPair = sanitizationMapping[opcode.name] | |
addendWordA.write(sanitizationPair.addendA.code.toString('hex'), j, 1, 'hex') | |
addendWordB.write(sanitizationPair.addendB.code.toString('hex'), j, 1, 'hex') | |
} | |
} | |
sanitizedConstant = sanitizedConstant.concat([ | |
/* | |
Stack: | |
0: <pointer to free memory> | |
*/ | |
// Push both addends onto the stack. We'll need to combine them to get | |
// our target constant back. | |
Opcode.PUSH32.code, | |
addendWordA, | |
Opcode.PUSH32.code, | |
addendWordB, | |
/* | |
Stack: | |
0: <addendWordB> | |
1: <addendWordA> | |
2: <pointer to free memory> | |
*/ | |
// Combine the addends into the target constant. | |
Opcode.ADD.code, | |
/* | |
Stack: | |
0: <addendWordB + addendWordA> | |
1: <addendWordB> | |
2: <addendWordA> | |
3: <pointer to free memory> | |
*/ | |
// Clean up the stack. | |
Opcode.SWAP1.code, | |
Opcode.POP.code, | |
Opcode.SWAP1.code, | |
Opcode.POP.code, | |
/* | |
Stack: | |
0: <addendWordB + addendWordA> | |
1: <pointer to free memory> | |
*/ | |
// Swap the memory location to the top of the stack. | |
Opcode.SWAP1.code, | |
/* | |
Stack: | |
0: <pointer to free memory> | |
1: <addendWordB + addendWordA> | |
*/ | |
// Push the byte offset onto the stack. | |
Opcode.PUSH1.code, | |
numberToBuffer(currentByteIndex + extraOffset), | |
/* | |
Stack: | |
0: <byte index, (word index + 32 if array, 0 otherwise)> | |
1: <pointer to free memory> | |
2: <addendWordB + addendWordA> | |
*/ | |
// Add the byte offset to the current memory location. | |
Opcode.ADD.code, | |
/* | |
Stack: | |
0: <byte index + pointer to free memory> | |
1: <byte index, (word index + 32 if array, 0 otherwise)> | |
2: <pointer to free memory> | |
3: <addendWordB + addendWordA> | |
*/ | |
// Remove the offset from the stack. | |
Opcode.SWAP1.code, | |
Opcode.POP.code, | |
/* | |
Stack: | |
0: <byte index + pointer to free memory> | |
1: <pointer to free memory> | |
2: <addendWordB + addendWordA> | |
*/ | |
// Move the target constant to the second stack index. | |
Opcode.SWAP2.code, | |
Opcode.SWAP1.code, | |
/* | |
Stack: | |
0: <byte index + pointer to free memory> | |
1: <addendWordB + addendWordA> | |
2: <pointer to free memory> | |
*/ | |
// Store the target constant at the memory location. | |
Opcode.MSTORE.code, | |
// Clean up after ourselves. | |
Opcode.POP.code, | |
Opcode.POP.code, | |
/* | |
Stack: | |
0: <pointer to free memory> | |
*/ | |
]) | |
} | |
sanitizedConstant = sanitizedConstant.concat([ | |
/* | |
Stack: | |
0: <pointer to free memory> | |
*/ | |
// Push the byte offset onto the stack. | |
Opcode.PUSH1.code, | |
numberToBuffer(totalWords * 32 + extraOffset), | |
/* | |
Stack: | |
1: <byte index, (total words + 32 if array, total words otherwise)> | |
0: <pointer to free memory> | |
*/ | |
// Add the byte offset to the current memory location. | |
Opcode.ADD.code, | |
/* | |
Stack: | |
0: <byte index + pointer to free memory> | |
1: <byte index, (total words + 32 if array, total words otherwise)> | |
2: <pointer to free memory> | |
*/ | |
// Clean up the stack. | |
Opcode.SWAP1.code, | |
Opcode.POP.code, | |
/* | |
Stack: | |
0: <byte index + pointer to free memory> | |
1: <pointer to free memory> | |
*/ | |
// Push the free memory pointer to the stack. | |
Opcode.PUSH1.code, | |
Buffer.from('40', 'hex'), | |
/* | |
Stack: | |
0: 0x40 | |
1: <byte index + pointer to free memory> | |
2: <pointer to free memory> | |
*/ | |
// Store the total word size in the pointer. | |
Opcode.MSTORE.code, | |
// Remove the free memory pointer from the stack. | |
Opcode.POP.code, | |
/* | |
Stack: | |
0: <byte index + pointer to free memory> | |
1: <pointer to free memory> | |
*/ | |
// Remove the final offset from the stack. | |
Opcode.POP.code, | |
/* | |
Stack: | |
0: <pointer to free memory> | |
*/ | |
]) | |
if (totalWords === 1) { | |
// When we're only dealing with a single storage slot, we want to place the | |
// actual value on the top of the stack (instead of a pointer). Here we | |
// load the value at the given pointer and remove the pointer from the stack. | |
sanitizedConstant = sanitizedConstant.concat([ | |
/* | |
Stack: | |
0: <pointer to free memory> | |
*/ | |
// Load the constant value from memory. | |
Opcode.MLOAD.code, | |
/* | |
Stack: | |
0: <constant value> | |
1: <pointer to free memory> | |
*/ | |
// Remove the pointer from the stack. | |
Opcode.SWAP1.code, | |
Opcode.POP.code, | |
/* | |
Stack: | |
0: <constant value> | |
*/ | |
]) | |
} | |
return Buffer.from(sanitizedConstant.map((byte) => { | |
byte.toString('hex') | |
}).join('')) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment