Skip to content
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 Shift Amount in Proxy #868

Merged
merged 4 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion contracts/proxies/SafeProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ contract SafeProxy {
let _singleton := sload(0)
// 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s
if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) {
mstore(0, shr(12, shl(12, _singleton)))
// We mask the singleton address when handling the `masterCopy()` call to ensure that it is correctly
// ABI-encoded. We do this by shifting the address left by 96 bits (or 12 bytes) and then storing it in
// memory with a 12 byte offset from where the return data starts. Note that we **intentionally** only
// do this for the `masterCopy()` call, since the EVM `DELEGATECALL` opcode ignores the most-significant
// 12 bytes from the address, so we do not need to make sure the top bytes are cleared when proxying
// calls to the `singleton`. This saves us a tiny amount of gas per proxied call.
mstore(0x0c, shl(96, _singleton))
return(0, 0x20)
}
calldatacopy(0, 0, calldatasize())
Expand Down
51 changes: 51 additions & 0 deletions test/factory/Proxy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect } from "chai";
import hre from "hardhat";
import { AddressZero } from "@ethersproject/constants";
import { deployContractFromSource } from "../utils/setup";

describe("Proxy", () => {
describe("constructor", () => {
Expand All @@ -9,4 +10,54 @@ describe("Proxy", () => {
await expect(Proxy.deploy(AddressZero)).to.be.revertedWith("Invalid singleton address provided");
});
});

describe("masterCopy", () => {
const SINGLETON_SOURCE = `
contract Test {
uint256 _singletonSlot;
function masterCopy() public pure returns (address) {
return address(0);
}
function overwriteSingletonSlot(uint256 value) public {
_singletonSlot = value;
}
function theAnswerToLifeTheUniverseAndEverything() public pure returns (uint256) {
return 42;
}
}`;

const setupTests = hre.deployments.createFixture(async () => {
const [deployer] = await hre.ethers.getSigners();
const singleton = await deployContractFromSource(deployer, SINGLETON_SOURCE);
const Proxy = await hre.ethers.getContractFactory("SafeProxy");
const proxyDeployment = await Proxy.deploy(singleton.target);
const proxy = singleton.attach(proxyDeployment) as typeof singleton;
return {
singleton,
proxy,
};
});

it("should return the master copy address regardless of implementation", async () => {
const { singleton, proxy } = await setupTests();
expect(await singleton.masterCopy()).to.equal(hre.ethers.ZeroAddress);
expect(await proxy.masterCopy()).to.equal(await singleton.getAddress());
});

it("should correctly mask the address value", async () => {
const { proxy } = await setupTests();
await proxy.overwriteSingletonSlot(hre.ethers.MaxUint256);
expect(await proxy.masterCopy()).to.equal(hre.ethers.getAddress(`0x${"ff".repeat(20)}`));
});

it("should ignore most significant bits when calling singleton", async () => {
const { singleton, proxy } = await setupTests();
const singletonAddressAsUint = BigInt(await singleton.getAddress());
const mask = 0xffffffffffffffffffffffffn << 160n;

expect(await proxy.theAnswerToLifeTheUniverseAndEverything()).to.equal(42);
await proxy.overwriteSingletonSlot(singletonAddressAsUint | mask);
expect(await proxy.theAnswerToLifeTheUniverseAndEverything()).to.equal(42);
});
});
});
Loading