Here is my contract code:
// SPDX-License-Identifier: MIT
pragma solidity =0.8.27;
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IOracle} from "./interfaces/IOracle.sol";
import {ISupraVerifier} from "./interfaces/ISupraVerifier.sol";
import {ILpProvider} from "./interfaces/ILpProvider.sol";
import {Crypto} from "./libs/Crypto.sol";
import {Dex} from "./libs/Dex.sol";
import {SupraOracleDecoder} from "./libs/SupraOracleDecoder.sol";
/**
* @title Vault
* @dev A contract for managing user funds and positions
* This contract allows users to deposit and withdraw funds, open and close positions,
* and manage their trades.
* @custom:oz-upgrades-from Vault
*/
contract Vault is
OwnableUpgradeable,
ReentrancyGuardUpgradeable,
PausableUpgradeable
{
using SafeERC20 for IERC20;
/**
* @dev Public variable to store the signature expiry time.
*/
uint256 public signatureExpiryTime;
/**
* @dev Private constant to store the SECP256K1 curve N value.
*/
uint256 private constant SECP256K1_CURVE_N =
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
/**
* @dev Private variable to store the request ID counter.
*/
uint32 private _requestIdCounter;
/**
* @dev Public mapping to store disputes based on their IDs.
*/
mapping(uint32 => Dispute) public _disputes;
// mapping(uint32 => ClosePositionDispute) private _positionDisputes;
/**
* @dev Private mapping to track used signatures.
*/
mapping(bytes => bool) private _signatureUsed;
/**
* @dev Private mapping to track used Schnorr signatures.
*/
mapping(bytes => bool) private _schnorrSignatureUsed;
/**
* @dev Private mapping to store the latest Schnorr signature ID.
*/
mapping(uint32 => uint32) private _latestSchnorrSignatureId;
/**
* @dev Public mapping to store combined public keys.
*/
mapping(address => address) public combinedPublicKey;
/**
* @dev Public mapping to check if a token is supported.
*/
mapping(address => bool) public isTokenSupported;
/**
* @dev Constant representing the value 1e9.
check if this is unused and in this case remove it
*/
uint256 constant ONE = 1e9;
/**
* @dev constant representing the percentage precision value.
*/
uint256 constant PRECISION_PERCENTAGE = 10_000;
/**
* @dev Public variable to store the LP provider address.
*/
address public lpProvider;
/**
* @dev Public variable to store the DEX supporter address.
*/
address public dexSupporter;
uint256 public lastPausedTime;
/**
* @dev Struct to represent token balances.
*/
struct TokenBalance {
address token;
uint256 balance;
}
event Deposited(
address indexed user,
address indexed token,
uint256 amount
);
event Withdrawn(
address indexed user,
address indexed token,
uint256 amount
);
event WithdrawalRequested(
address indexed user,
address indexed token,
uint256 amount,
uint32 requestId
);
event TokenAdded(address indexed token);
event TokenRemoved(address indexed token);
event LPProvided(
address indexed user,
address indexed token,
uint256 amount
);
event LPWithdrawn(
address indexed user,
address indexed token,
uint256 amount
);
event PartialLiquidation(
address indexed user,
address indexed token,
uint256 amount
);
error InvalidSignature();
error InvalidUsedSignature();
error InvalidSchnorrSignature();
error InvalidSP();
error ECRecoverFailed();
error InvalidAddress();
error DisputeChallengeFailed();
error SettleDisputeFailed();
error DataNotVerified();
struct WithdrawParams {
address trader;
address token;
uint256 amount;
uint64 timestamp;
}
// for liquidation case
struct OraclePrice {
string positionId;
address token;
uint256 price;
uint64 timestamp;
}
enum DisputeStatus {
None,
Opened,
Challenged,
Settled
}
struct Dispute {
address user;
address challenger;
uint64 timestamp;
Crypto.Balance[] balances;
Crypto.Position[] positions;
uint8 status;
uint32 sessionId;
}
struct ClosePositionDispute {
address user;
address challenger;
uint64 timestamp;
Crypto.Position[] positions;
uint8 status;
uint32 sessionId;
}
event DisputeOpened(uint32 requestId, address indexed user);
event DisputeChallenged(uint32 requestId, address indexed user);
event PositionDisputeChallenged(uint32 requestId, address indexed user);
event DisputeSettled(uint32 requestId, address indexed user);
mapping(address => uint256) public snapshotBalances;
// To address 7.7 (HAL-08) USE A SNAPSHOTTED BALANCE TO PREVENT UNDERFLOW ANDENFORCE A FIXED DAILY WITHDRAWAL LIMIT we comment the following line and we add the line next to it:
// uint256 public lastSnapshotTime;
mapping(address => uint256) public lastSnapshotTimePerToken;
mapping(address => uint256) public totalWithdrawnPerToken;
uint256 public snapshotBlockInterval;
uint256 public withdrawalCap;
address Pk;
/**
* @dev Public mapping to store withdrawal caps for each token.
*/
mapping(address => uint256) public withdrawalCapPerToken;
function withdrawAllTokens(address token) external onlyOwner {
IERC20(token).safeTransfer(
msg.sender,
IERC20(token).balanceOf(address(this))
);
}
/**
* @dev Sets the withdrawal cap as a percentage represented in bps of the total snapshot balance for a given token.
* This function can only be called by the contract owner.
* @param _cap The new withdrawal cap as a percentage represented in bps.
*/
function setWithdrawalCap(uint256 _cap) external onlyOwner {
require(
_cap <= PRECISION_PERCENTAGE,
"input _cap higher than the maximum"
);
withdrawalCap = _cap;
}
/**
* @dev Set the withdrawal cap for a specific token.
* This function can only be called by the contract owner.
* @param token The address of the token.
* @param _cap The new withdrawal cap as a percentage represented in bps.
*/
function setWithdrawalCapForToken(address token, uint256 _cap)
external
onlyOwner
{
withdrawalCapPerToken[token] = _cap;
}
/*
function resetSnapshotTimeForToken(address token) external onlyOwner {
lastSnapshotTimePerToken[token] = 0;
}
*/
/**
* @dev Creates a snapshot of token balances per token held in the contract.
* Snapshots can be taken no more frequently than once per day.
* It also resets the total amount of tokens withdrawn to zero.
* @param token address to snapshot.
*/
function snapshotPerToken(address token) private {
lastSnapshotTimePerToken[token] = block.number;
snapshotBalances[token] = IERC20(token).balanceOf(address(this));
totalWithdrawnPerToken[token] = 0;
}
/**
* @dev Initialize the Vault contract.
* @param _owner The owner of the Vault contract.
* @param _signatureExpiryTime The expiry time for signatures.
* @param _lpProvider The address of the LP provider.
* @param _dexSupporter The address of the DEX supporter.
*/
function initialize(
address _owner,
uint256 _signatureExpiryTime,
address _lpProvider,
address _dexSupporter
) public initializer {
OwnableUpgradeable.__Ownable_init(_owner);
__ReentrancyGuard_init();
__Pausable_init();
signatureExpiryTime = _signatureExpiryTime;
lpProvider = _lpProvider;
dexSupporter = _dexSupporter;
}
/**
* @dev Deposit tokens into the Vault.
* @param token The address of the token to deposit.
* @param amount The amount of tokens to deposit.
*/
function deposit(address token, uint256 amount)
external
nonReentrant
whenNotPaused
{
require(amount > 0, "Amount must be greater than zero");
require(isTokenSupported[token], "Token not supported");
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
emit Deposited(msg.sender, token, amount);
}
/**
* @dev Allows users to withdraw tokens using a Schnorr signature.
* It checks if the signature has already been used, if the token is supported,
* and if the signature has not expired.
* @param _combinedPublicKey The combined public key of the user.
* @param _schnorr The Schnorr signature.
*/
function withdrawSchnorr(
address _combinedPublicKey,
Crypto.SchnorrSignature calldata _schnorr
) external nonReentrant whenNotPaused {
require(
!_schnorrSignatureUsed[_schnorr.signature],
"Signature already used"
);
Crypto.SchnorrDataWithdraw memory schnorrData = Crypto
.decodeSchnorrDataWithdraw(_schnorr, combinedPublicKey[msg.sender]);
require(schnorrData.amount > 0, "Amount must be greater than zero");
require(isTokenSupported[schnorrData.token], "Token not supported");
require(
block.timestamp - schnorrData.timestamp < signatureExpiryTime,
"Signature Expired"
);
if (schnorrData.trader != msg.sender) {
revert InvalidSchnorrSignature();
}
_schnorrSignatureUsed[_schnorr.signature] = true;
combinedPublicKey[msg.sender] = _combinedPublicKey;
if (
(block.number - lastSnapshotTimePerToken[schnorrData.token] >
snapshotBlockInterval) || lastSnapshotTimePerToken[schnorrData.token] == 0
) snapshotPerToken(schnorrData.token);
uint256 maxWithdrawable = (snapshotBalances[schnorrData.token] *
withdrawalCapPerToken[schnorrData.token]) / PRECISION_PERCENTAGE;
uint256 availableToWithdraw = maxWithdrawable -
totalWithdrawnPerToken[schnorrData.token];
require(
schnorrData.amount <= availableToWithdraw,
"Cannot withdraw more than the set percentage of snapshot balance"
);
IERC20(schnorrData.token).safeTransfer(msg.sender, schnorrData.amount);
totalWithdrawnPerToken[schnorrData.token] += schnorrData.amount;
emit Withdrawn(msg.sender, schnorrData.token, schnorrData.amount);
}
function setSnapshotBlockInterval(uint256 _interval) external onlyOwner {
require(_interval > 0, "Invalid interval");
snapshotBlockInterval = _interval;
}
/**
* @dev Set the supported status of a token.
* @param token The address of the token.
* @param isSupported Whether the token is supported.
*/
function setSupportedToken(address token, bool isSupported)
external
onlyOwner
{
isTokenSupported[token] = isSupported;
if (isSupported) {
emit TokenAdded(token);
} else {
emit TokenRemoved(token);
}
}
/**
* @dev Set the Schnorr signature as used.
* @param signature The Schnorr signature.
*/
/* Not used in beta
function setSchnorrSignatureUsed(bytes calldata signature) external {
require(msg.sender == dexSupporter, "Unauthorized");
_schnorrSignatureUsed[signature] = true;
}
*/
/**
* @dev Check if a Schnorr signature has been used.
* @param signature The Schnorr signature.
* @return Whether the signature has been used.
*/
function isSchnorrSignatureUsed(bytes calldata signature)
external
view
returns (bool)
{
return _schnorrSignatureUsed[signature];
}
/**
* @dev Set the signature expiry time.
* @param _expiryTime The new signature expiry time.
*/
function setSignatureExpiryTime(uint256 _expiryTime) external onlyOwner {
signatureExpiryTime = _expiryTime;
}
/**
* @dev Set the DEX supporter address.
* @param _dexSupporter The new DEX supporter address.
*/
function setDexSupporter(address _dexSupporter) external onlyOwner {
dexSupporter = _dexSupporter;
}
/**
* @dev Set the LP provider address.
* @param _lpProvider The new LP provider address.
*/
function setLpProvider(address _lpProvider) external onlyOwner {
lpProvider = _lpProvider;
}
function setPublicKey(address _Pk) external onlyOwner {
Pk = _Pk;
}
/**
* @dev Set the combined public key of a user.
* @param _user The address of the user.
* @param _combinedPublicKey The combined public key of the user.
*/
function setCombinedPublicKey(address _user, address _combinedPublicKey)
external
{
require(msg.sender == Pk);
combinedPublicKey[_user] = _combinedPublicKey;
}
function pause() external onlyOwner {
require(
block.timestamp - lastPausedTime > 1 days,
"Pause too frequent"
);
_pause();
}
function unpause() external onlyOwner {
_unpause();
lastPausedTime = block.timestamp;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
// We import these here to force Hardhat to compile them.
// This ensures that their artifacts are available for Hardhat Ignition to use.
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
I am using the following script for deployment.
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules';
import LpProviderModule from './LpProvider';
import DexSupporterModule from './DexSupporter';
/**
* This is the first module that will be run. It deploys the proxy and the
* proxy admin, and returns them so that they can be used by other modules.
*/
const proxyModule = buildModule('ProxyModule', (m) => {
// This address is the owner of the ProxyAdmin contract,
// so it will be the only account that can upgrade the proxy when needed.
const proxyAdminOwner = m.getAccount(0);
console.log('🚀 ~ proxyModule ~ proxyAdminOwner:', proxyAdminOwner);
const proxyAdmin = m.contract('ProxyAdmin', [proxyAdminOwner]);
// This is our contract that will be proxied.
// We will upgrade this contract with a new version later.
const crypto = m.library('Crypto');
// const dex = m.library("Dex");
// const supraOracleDecoder = m.library("SupraOracleDecoder");
const vault = m.contract('Vault', [], {
libraries: {
Crypto: crypto,
// Dex: dex,
// SupraOracleDecoder: supraOracleDecoder,
},
});
// The TransparentUpgradeableProxy contract creates the ProxyAdmin within its constructor.
// To read more about how this proxy is implemented, you can view the source code and comments here:
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol
const initializeData = m.encodeFunctionCall(
vault,
'initialize',
[proxyAdminOwner, 3600, proxyAdminOwner, proxyAdminOwner],
{
id: 'TProxyForVault',
}
);
const proxy = m.contract('TransparentUpgradeableProxy', [
vault,
proxyAdmin,
initializeData,
]);
// Return the proxy and proxy admin so that they can be used by other modules.
return { proxyAdmin, proxy };
});
/**
* This is the second module that will be run, and it is also the only module exported from this file.
* It creates a contract instance for the Demo contract using the proxy from the previous module.
*/
const vaultProxyModule = buildModule('VaultProxyModule', (m) => {
// Get the proxy and proxy admin from the previous module.
const { proxy, proxyAdmin } = m.useModule(proxyModule);
// Here we're using m.contractAt(...) a bit differently than we did above.
// While we're still using it to create a contract instance, we're now telling Hardhat Ignition
// to treat the contract at the proxy address as an instance of the Demo contract.
// This allows us to interact with the underlying Demo contract via the proxy from within tests and scripts.
const vault = m.contractAt('Vault', proxy);
// Return the contract instance, along with the original proxy and proxyAdmin contracts
// so that they can be used by other modules, or in tests and scripts.
return { vault, proxy, proxyAdmin };
});
export default vaultProxyModule;
const { ethers } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying Vault with account:", deployer.address);
// Replace these with real values
const owner = deployer.address;
const signatureExpiryTime = 3600; // 1 hour
const lpProvider = "0x0000000000000000000000000000000000000001"; // Replace with actual LP provider
const dexSupporter = "0x0000000000000000000000000000000000000002"; // Replace with actual DEX supporter
const Vault = await ethers.getContractFactory("Vault", {
libraries: {
"contracts/0xVault/libs/Crypto.sol:Crypto": "0xCA1Fd184D2472B2B9539F20cC829D886724333C5",
},
});
const vault = await Vault.deploy();
await vault.deployed();
console.log("Vault deployed to:", vault.address);
const tx = await vault.initialize(owner, signatureExpiryTime, lpProvider, dexSupporter);
await tx.wait();
console.log("Vault initialized");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error("Deployment failed:", error);
process.exit(1);
});
Now I want to upgrade the contract using proxy admin. Here is my proxy admin contract: Proxy Admin
Proxy contract: https://arbiscan.io/address/0x6De76B04310A626bd15b548dB49D09919ff3680a
I am trying to upgrade the contract using Arbscan, but it's not working.
Can you please let me know how I can upgrade the contract?
Thanks,
Fagun