Skip to content

Instantly share code, notes, and snippets.

@smartcontracts
Created July 1, 2020 20:03
Show Gist options
  • Save smartcontracts/7c0c9dc98bbb4392493bc60ce4b0b93f to your computer and use it in GitHub Desktop.
Save smartcontracts/7c0c9dc98bbb4392493bc60ce4b0b93f to your computer and use it in GitHub Desktop.
PoC for the constant modification idea
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