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
fix: expand memory on reading prev. untouched location #1887
Conversation
Memory is expanded by word when accessing previously untouched memory word ([relevant docs](https://docs.soliditylang.org/en/v0.8.13/introduction-to-smart-contracts.html#storage-memory-and-the-stack)). That applies to read operation on memory too.
Hi there, Did this have some practical implications for you, I am rather curious, how did you stumble upon this? Also: am I correct that the main implication of this that this would/will change gas costs in some certain circumstances? If this is the case I am a bit confused why this bug/misbehavior has not been detected by the official //cc @jochem-brouwer (other) |
Codecov Report
Flags with carried forward coverage won't be shown. Click here to find out more. |
Hey, I was actually coding up a mini - evm in rust and was visually cross-checking evm's behavior on evm.codes after digging deep into docs/papers for my project.
Running the above code should increase memory size to 64 ( vm.on('step', function (data) {
// data.memory.length is 0 here, instead of 64!
}) Regarding the gas costs - yes, expansion - if any - does cost a bit of extra gas during read/write to memory. I couldn't find any tests that specifically tested the expansion behavior of memory during read/write operations. This case must've been missed most probably because expansion is not actually a part of read/write methods in source code. Unless there's some specific requirement I'm unaware of, I think write(offset: number, size: number, value: Buffer) {
this.extend(offset, size); // extend memory automatically
if (size === 0) {
return
}
assert(value.length === size, 'Invalid value size')
assert(offset + size <= this._store.length, 'Value exceeds memory capacity')
assert(Buffer.isBuffer(value), 'Invalid value type')
for (let i = 0; i < size; i++) {
this._store[offset + i] = value[i]
}
}
/**
* Similarly for `read` method
*/ ...instead of doing an |
Thanks for opening this PR! I will double-check this but this should not change gas costs. This will indeed influence the
This does update the gas-related fields like the |
Just checked; const vm = await VM.create()
const runCodeArgs = {
code: Buffer.from('602051', 'hex'),
gasLimit: BigInt(0xffff),
}
const result = await vm.runCode(runCodeArgs)
console.log(result.gasUsed) This runs On both |
I think we should add the |
@jochem-brouwer ah, thanks for the in-depth analysis, that is really interesting, took me a while to grasp why this is not a consensus bug. So how would you recommend that we proceed here? Should we merge this one in and you would do an on-top-PR? Would you directly do an alternative PR? Other options? 🙂 |
@theNvN ah, and thanks for your extensive explanatory write-up as well! 🙏 ❤️ |
|
Nice. Yes, I think the related gas cost part is behaving correctly. This might be helpful. |
Yes. That's what I thought too!
And what about the |
I think the end goal of this PR is that the |
Sure 👍🏼. I can push additional changes. Just so we're on the same page - I should make these changes (?):
// const newMemoryWordCount = divCeil(offset.add(length), new BN(32)); // remove
const newMemoryWordCount = new BN(runState.memory._store.length / 32); // add Anything else..? Let me know. |
@theNvN Yes that is correct. Thanks a lot! 😄 |
Removed a couple of tests for write beyond capacity. This is because memory is now auto expanded during write.
@jochem-brouwer pushed changes except last one ( |
@theNvN CI does not show any failing tests. Which are failing? |
I didn't push that change. It is the last one in above (in This: const newMemoryWordCount = divCeil(offset.add(length), new BN(32)); is not always same as this: const newMemoryWordCount = new BN(runState.memory._store.length / 32); |
Huh that's weird, I do recall that I commented a warning about that on here, but apparently I did not. const newMemoryWordCount = divCeil(new BN(runState.memory.length), new BN(32)) This should work. |
Nope. Still failing...probably the same tests (incl. An interesting thing I find is that gas cost is subtracted first: and then opcode handler is executed later: |
Ahh yes you are on the right track 👍! You are right that gas first gets calculated and then the actual function is executed. Thanks a lot 😄 👍 |
cool! |
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.
Thanks, LGTM!
@theNvN nice, thanks a lot! 👍 🙂 |
my pleasure 😄 👍 |
Memory is expanded by word when accessing previously untouched memory word (relevant docs). That applies to read operation on memory too.