I've written a simple proxy contract with solidity and I've got an issue with the variables inside the delegate contract. When I delegateCall, all my variables are equal to 0, except if there are constant. Is there any reason for that or am I missing something ?
My proxy contract :
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
contract Proxy {
mapping(string => address) public strategies;
function addStrategy(string memory id, address implementation) external {
strategies[id] = implementation;
}
function removeStrategy(string memory id) external {
delete strategies[id];
}
function displayVar(string memory strategyId) external {
address strategy = strategies[strategyId];
require(strategy != address(0x0), "Strategy not found..");
(bool success, bytes memory data) = strategy.delegatecall(
abi.encodeWithSignature("displayVar()")
);
}
}
The deleguate contract :
pragma solidity ^0.8.3;
import "hardhat/console.sol";
contract Delegate {
mapping(string => address) public strategies;
address public constant CRV = 0xD533a949740bb3306d119CC777fa900bA034cd52;
address public curve = 0x90E00ACe148ca3b23Ac1bC8C240C2a7Dd9c2d7f5;
address public constant cvx = 0xF403C135812408BFbE8713b5A23a04b3D48AAE31;
address public constant CVX = 0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B;
function displayVar() external returns (bool) {
console.log(CRV);
console.log(curve);
console.log(cvx);
console.log(CVX);
}
}
the test with HardHat :
import { Contract, ContractFactory } from "ethers";
import { ethers } from "hardhat";
describe("test via proxy", function () {
let Proxy: ContractFactory, proxy: Contract;
let Delegate: ContractFactory, delegate: Contract;
const stratName = "test";
before(async function () {
Proxy = await ethers.getContractFactory("Proxy");
proxy = await Proxy.deploy();
await proxy.deployed();
Delegate = await ethers.getContractFactory("Delegate");
delegate = await Delegate.deploy();
await delegate.deployed();
await proxy.addStrategy(stratName, delegate.address);
});
it("should display", async function () {
const [owner] = await ethers.getSigners();
await proxy.connect(owner).displayVar(stratName);
});
});
And finally the output is :
0xd533a949740bb3306d119cc777fa900ba034cd52
0x0000000000000000000000000000000000000000
0xf403c135812408bfbe8713b5a23a04b3d48aae31
0x4e3fbd56cd56c3e72c1403e103b45db9da5b9d2b
CodePudding user response:
A quick intro: When you use delegatecall, you are “using” the targeted contract’s code (in your case Delegate) but keeping the storage of the proxy. In other words, the storage of the proxy is completely independent (that is the purpose of the proxy, to be upgradable and / or to save gas on deployment.
With this in mind, your proxy contract can only use the code of delegate, but maintaining its own storage. But, constant and immutable variables do not occupy a storage slot, they are injected in the bytecode at compile time. That is why your proxy also has them. But all the other variables are defaulted to 0 (depending on the type).
CodePudding user response:
The point of using Proxy
contract is to keep the state of different implementation versions in the same contract via the delegatecall
. Therefore using constructor
is not a safe method inside the implementation contract because its state inside the constuctor is set inside the implementation contract when we deploy the contract: Solidity: Why use Initialize function instead of constructor?
Initializing storage variables is not safe either because those state variables are set inside the implementation contract when we deploy them like the constructor.
// this is not safe
// you should move the assignment inside an initializer function
// this is not set inside the proxy
address public curve = 0x90E00ACe148ca3b23Ac1bC8C240C2a7Dd9c2d7f5;
However, if you have constant
and immutable
variables, those variables will be inside the bytecode
of your contract and your proxy needs those variable, it will point to bytecode and get the code. Using constant
and immutable
is believed to be safe in this case. However, if you set those variables in V1 and then over time you upgraded to V2,V3 etc. If your V1 had selfdestruct
function and you killed the contract in the future, V1's bytecode will be deleted and therefore your proxy
will no longer have access to those variables.