Upgradable contract assembler

Hey,

I’ve recently been going over my understanding of transparent proxies, and noticed that at some-point there’s been a change:

Old code from OZ blog post

assembly {
  let ptr := mload(0x40)

  // (1) copy incoming call data
  calldatacopy(ptr, 0, calldatasize)

  // (2) forward call to logic contract
  let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
  let size := returndatasize

  // (3) retrieve return data
  returndatacopy(ptr, 0, size)

  // (4) forward return data back to caller
  switch result
  case 0 { revert(ptr, size) }
  default { return(ptr, size) }
}

New code from OZ contract:

assembly {
  // (1) copy incoming call data
  calldatacopy(0, 0, calldatasize())

  // (2) forward call to logic contract
  let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

  // (3) retrieve return data
  returndatacopy(0, 0, returndatasize())

  // (4) forward return data back to caller
  switch result
  case 0 { revert(0, returndatasize()) }
  default { return(0, returndatasize()) }
}

I know that let ptr := mload(0x40) used to be a special-slot containing the address of the next allocatable slot, so it made sense to me that it was used as the reference slot for some of these OPs, but now they use 0, is this some kind of shortcut which has been introduced? Or is there some assumption being made that 0 is the slot where the delegatcall will return its value?

P.S. This is a dupe from a post I made in security a few days ago, I now realise it should have been posted here - but I don’t appear to be able to delete my previous post.

Welcome to the forum @aftermath.digital. I’ve deleted your previous post.

This was changed because it is cheaper this way, and it’s not necessary to write in the free memory pointer (mload(0x40)). It’s not necessary because we will return immediately after writing this memory, so we don’t have to preserve any Solidity invariants and we can just write to memory starting at position 0.

There is no assumption made, delegatecall returns its value to a separate “returndata” memory space, and we use returndatacopy to copy it over to the general memory space, starting at position 0 because of what I mentioned in the previous paragraph.