I am working on a smart contract that interacts with a marketplace that is transparent proxy upgradeable and a lending protocol that is UUPS proxy upgradeable (the contract I am building is non upgradeable).
When I deploy the marketplace and lending protocol for testing purposes in Hardhat/Ethers, I get an error re. the UUPS upgradeable ones:
Error: Contract `ContractName` is not upgrade safe
@openzeppelin/contracts/utils/Address.sol:191: Use of delegatecall is not allowed
The upgradeable contracts already exist on Mainnet and do not have any issues.
Also, they use AddressUpgradeable.sol
vs. Address.sol.
Could this issue have to do with the presence of a Transparent proxy and UUPS upgradeable contracts in the same development environment?
This is my contract code:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/interfaces/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@arcadexyz/v2-contracts/contracts/external/interfaces/ILendingPool.sol";
import "@arcadexyz/v2-contracts/contracts/interfaces/IRepaymentController.sol";
import "@arcadexyz/v2-contracts/contracts/interfaces/ILoanCore.sol";
import "@arcadexyz/v2-contracts/contracts/interfaces/IAssetVault.sol";
import "@arcadexyz/v2-contracts/contracts/interfaces/IPromissoryNote.sol";
import "@arcadexyz/v2-contracts/contracts/InstallmentsCalc.sol";
import "@arcadexyz/v2-contracts/contracts/PromissoryNote.sol";
import "@arcadexyz/v2-contracts/contracts/interfaces/ICallWhitelist.sol";
import "./interfaces/IORsol";
import "./interfaces/ICS.sol";
import "./libraries/SaleLibrary.sol";
contract CS is ICS, IERC721Receiver, InstallmentsCalc, ReentrancyGuard, ERC721Holder {
using SafeERC20 for IERC20;
// ======================================== STATE ==================================================
bytes4 private constant _EIP_1271_MAGIC_VALUE = 0x1626ba7e;
/* solhint-disable var-name-mixedcase */
// AAVE Contracts
// Variable names are in upper case to fulfill IFlashLoanReceiver interface
ILendingPoolAddressesProvider public immutable override ADDRESSES_PROVIDER;
ILendingPool public immutable override LENDING_POOL;
/* solhint-enable var-name-mixedcase */
address private owner;
// IERC721Receiver variables
Error private immutable _error;
// ========================================== CONSTRUCTOR ===========================================
constructor(ILendingPoolAddressesProvider _addressesProvider) {
ADDRESSES_PROVIDER = _addressesProvider;
LENDING_POOL = ILendingPool(_addressesProvider.getLendingPool());
owner = msg.sender;
_error = Error.None;
}
function verifyLoanData(SaleLibrary.CSD memory csd, SaleLibrary.ContractParams memory contracts)
internal
returns (uint256, uint256)
{
....
uint256 _amountToBorrow; // amount to borrow from the flashLoan
uint256 extraAmount; // amount above and beyond loan amount
return (_amountToBorrow, extraAmount);
}
function fulfillCS(
SaleLibrary.ContractParams memory contracts,
SaleLibrary.CSD memory csd,
address _seller,
address _buyer
) external payable returns (bool) {
// invoke verify loan data verification functionality
uint256 amountToBorrow;
uint256 extraAmount;
(amountToBorrow, extraAmount) = verifyLoanData(csd, contracts);
address[] memory assets = new address[](1);
assets[0] = csd.basicOrderParams.offerToken;
uint256[] memory amounts = new uint256[](1);
amounts[0] = amountToBorrow;
uint256[] memory modes = new uint256[](1);
modes[0] = 0;
...
bytes memory params = abi.encode(SaleLibrary.FlashLoanParams(contracts, csd, borrowerData));
LENDING_POOL.flashLoan(
address(this), // address receiving the funds / implementing IFlashLoanReceiver
assets, // addresses of the assets being flash-borrowed
amounts, // amounts amounts being flash-borrowed
modes, // 0 -> Don't open any debt, just revert if funds can't be transferred from the receiver
address(this), // address receiving the debt in the case of using on `modes` 1 or 2
params, // packed params to pass to the receiver as extra information
0 // referralCode code. referralCode Code
);
return true;
}
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external override nonReentrant returns (bool) {
require(msg.sender == address(LENDING_POOL), "unknown callback sender");
require(initiator == address(this), "not initiator");
return _executeOperation(assets, amounts, premiums, params);
}
function _executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
bytes calldata params
) internal returns (bool) {
SaleLibrary.FlashLoanParams memory decoded = abi.decode(params, (SaleLibrary.FlashLoanParams));
SaleLibrary.ContractParams memory contracts = decoded.contracts;
SaleLibrary.CSD memory csd = decoded.csd;
SaleLibrary.BorrowerMetadata memory borrowerData = decoded.borrowerData;
// get the external contracts
OperationContracts memory opContracts = _getContracts(contracts);
...
opContracts.oR.fulfillOrder(csd.protocol, csd.targetMarket, csd.recipient, abi.encode(orderParams));
{
uint256 csBalance = asset.balanceOf(address(this));
uint256 amountToRepay;
amountToRepay = amounts[0] + premiums[0];
if (amountToRepay > csBalance) {
uint256 difference = amountToRepay - csBalance;
asset.transferFrom(borrowerData.borrower, address(this), difference);
}
}
{
// approve Aave to withdraw owed ERC20 funds after flashLoan execution
asset.approve(address(LENDING_POOL), amounts[0] + premiums[0]);
}
return true;
}
function _getContracts(SaleLibrary.ContractParams memory contracts) internal returns (OperationContracts memory) {
return
OperationContracts({
...
});
}
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes memory data
) public virtual override(ERC721Holder, IERC721Receiver) returns (bytes4) {
super.onERC721Received(operator, from, tokenId, data); //call parent hook
if (_error == Error.RevertWithMessage) {
revert("ERC721Receiver: reverting");
} else if (_error == Error.RevertWithoutMessage) {
//solhint-disable-next-line
revert();
} else if (_error == Error.Panic) {
uint256 a = uint256(0) / uint256(0);
a;
}
emit Received(operator, from, tokenId, data, gasleft());
return IERC721Receiver.onERC721Received.selector;
}
}
The imported interface IOR.sol
calls the Treasure Marketplace which is a Transparent proxy.
The UUPS proxy imports are:
ILoanCore.sol
IAssetVault.sol
And one inherited by IAssetVault.sol
.
This is how I am deploying the upgradeable contracts in my test:
Example of UUPS proxy deployment:
const VaultFactoryFactory = await hre.ethers.getContractFactory("VaultFactory");
const vaultFactory = <VaultFactory>await upgrades.deployProxy(
VaultFactoryFactory,
[vault.address, whitelist.address],
{
kind: "uups",
initializer: "initialize(address, address)",
timeout: 0
},
);
await vaultFactory.deployed();
and the transparent proxy:
const TreasureMarketplace = await ethers.getContractFactory("TreasureMarketplace");
const marketplaceFee: number = 50;
const treasureMarketplace = <TreasureMarketplace>(await upgrades.deployProxy(
TreasureMarketplace,
[marketplaceFee, owner.address, testERC20.address],
{ kind: "transparent", initializer: "initialize" },
));
The test work well and passes when I silence the error by using unsafeAllow: ['delegatecall']
, but I cannot rely on that for going to Mainnet.
This is a screenshot of the error:
When using unsafeAllow: ['delegatecall']
, with passing tests, I still get this warning: