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

weETH withdrawal: Instant withdrawal with Fee + Implicit withdrawal fee handling #207

Open
wants to merge 99 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
d356cc9
init. Instant Withdrawal via Buffer
seongyun-ko Dec 6, 2024
82fab2e
implemented instant fee mechanism, handling of the implicit fee
seongyun-ko Dec 9, 2024
0f13953
fix scripts
seongyun-ko Dec 12, 2024
0b68310
add role registry, consider eth amount locked for withdrawal in liqui…
seongyun-ko Dec 17, 2024
c9fd604
use setter for 'shareRemainderSplitToTreasuryInBps', add more fuzz te…
seongyun-ko Dec 19, 2024
c377e8e
handle issues in calculating the dust shares
seongyun-ko Dec 20, 2024
a3aeac9
improve comments
seongyun-ko Dec 20, 2024
57bd0fc
add sorted & unique constraints + type change to reduce gas
seongyun-ko Dec 24, 2024
b6100b6
update 'handleAccumulatedShareRemainder' to be callable by admin
seongyun-ko Dec 24, 2024
e8734d6
Certora audit: (1) add {aggregateSumEEthShareAmount}, (2) fix {_claim…
seongyun-ko Dec 26, 2024
71ffa8d
wip: to be amended
seongyun-ko Dec 30, 2024
9e0fb99
add simplified {invalidate, validate} request, fix unit tests
seongyun-ko Dec 30, 2024
98f483a
rename EtherFiWithdrawBuffer -> EtherFiRedemptionManager
seongyun-ko Dec 30, 2024
bcc1184
fix the logic to check the aggr calls
seongyun-ko Dec 30, 2024
1f85fb6
Update test/WithdrawRequestNFT.t.sol
jtfirek Dec 30, 2024
bdee463
reduce gas spending for 'call', update the upgrade init function, rem…
seongyun-ko Dec 30, 2024
d40a117
apply gas opt for BucketLimiter
seongyun-ko Dec 30, 2024
f4f2ffd
improve assetion tsets, apply design pattern, function rename
seongyun-ko Dec 30, 2024
5069c14
apply CEI pattern to 'handleRemainder'
seongyun-ko Dec 30, 2024
2e13202
apply CEI pattern to 'redeem'
seongyun-ko Dec 31, 2024
b18bd18
use 'totalRemainderEEthShares' instead of locked share
seongyun-ko Dec 31, 2024
1e12673
initializeOnUpgrade cant be called twice
seongyun-ko Dec 31, 2024
c14c348
initializeOnUpgrade onlyOnce
seongyun-ko Dec 31, 2024
35fba66
use uint256 instead of uint32
seongyun-ko Dec 31, 2024
e95cfc0
revert
seongyun-ko Dec 31, 2024
bbf2d83
improve the fuzz test
seongyun-ko Dec 31, 2024
2cbbc04
(1) pause the contract on upgrade, (2) prevent from calling 'aggregat…
seongyun-ko Jan 1, 2025
1482cb0
only owner of the funds can call {redeemEEth, redeemWeEth}
seongyun-ko Jan 2, 2025
8ced81e
disable unpause until the scan is completed
seongyun-ko Jan 2, 2025
86ac4a0
check the basis points params are below 1e4
seongyun-ko Jan 2, 2025
1e8cdea
disallow calling 'initializeOnUpgradeWithRedemptionManager' with inva…
seongyun-ko Jan 2, 2025
6fffba6
(1) withdrawRequestNFT cannot call LiquidityPool.addEthAmountLockedFo…
seongyun-ko Jan 2, 2025
89db9d5
prevent finalizing future requests
seongyun-ko Jan 2, 2025
c5a2dd1
add {redeemEEthWithPermit, redeemWeEthWithPermit}, use try-catch for
seongyun-ko Jan 2, 2025
c85e920
remove a redundant check
seongyun-ko Jan 2, 2025
cca361e
Prevent 'initializeOnUpgrade' of LiquidityPool from being called twic…
seongyun-ko Jan 2, 2025
268efe5
use 'isScanOfShareRemainderCompleted'
seongyun-ko Jan 2, 2025
a13919d
add the max value constraint on rate limit
seongyun-ko Jan 2, 2025
58fe3fb
remove equality condition
seongyun-ko Jan 2, 2025
898896a
fix the overflow issue in '_refill'
seongyun-ko Jan 2, 2025
25ac914
Update EtherFiRedemptionManager.sol
seongyun-ko Jan 2, 2025
fd52a52
prevent the total shares from going below 1 gwei after redemption
seongyun-ko Jan 2, 2025
6eefec0
init. Instant Withdrawal via Buffer
seongyun-ko Dec 6, 2024
9963471
implemented instant fee mechanism, handling of the implicit fee
seongyun-ko Dec 9, 2024
9dcb732
fix scripts
seongyun-ko Dec 12, 2024
0e35c6b
add role registry, consider eth amount locked for withdrawal in liqui…
seongyun-ko Dec 17, 2024
77e61d8
use setter for 'shareRemainderSplitToTreasuryInBps', add more fuzz te…
seongyun-ko Dec 19, 2024
1435cc8
handle issues in calculating the dust shares
seongyun-ko Dec 20, 2024
8565d21
improve comments
seongyun-ko Dec 20, 2024
805bf07
add sorted & unique constraints + type change to reduce gas
seongyun-ko Dec 24, 2024
2de521a
update 'handleAccumulatedShareRemainder' to be callable by admin
seongyun-ko Dec 24, 2024
fc4a179
Certora audit: (1) add {aggregateSumEEthShareAmount}, (2) fix {_claim…
seongyun-ko Dec 26, 2024
8acd291
wip: to be amended
seongyun-ko Dec 30, 2024
a7e974b
add simplified {invalidate, validate} request, fix unit tests
seongyun-ko Dec 30, 2024
83bcd4f
rename EtherFiWithdrawBuffer -> EtherFiRedemptionManager
seongyun-ko Dec 30, 2024
34f67b9
fix the logic to check the aggr calls
seongyun-ko Dec 30, 2024
da5af14
Update test/WithdrawRequestNFT.t.sol
jtfirek Dec 30, 2024
577587b
reduce gas spending for 'call', update the upgrade init function, rem…
seongyun-ko Dec 30, 2024
980af7d
apply gas opt for BucketLimiter
seongyun-ko Dec 30, 2024
599d287
improve assetion tsets, apply design pattern, function rename
seongyun-ko Dec 30, 2024
5bd6630
apply CEI pattern to 'handleRemainder'
seongyun-ko Dec 30, 2024
2dd2115
apply CEI pattern to 'redeem'
seongyun-ko Dec 31, 2024
fbcd9f1
use 'totalRemainderEEthShares' instead of locked share
seongyun-ko Dec 31, 2024
3730663
initializeOnUpgrade cant be called twice
seongyun-ko Dec 31, 2024
72172e1
initializeOnUpgrade onlyOnce
seongyun-ko Dec 31, 2024
9dd6db1
use uint256 instead of uint32
seongyun-ko Dec 31, 2024
b61899b
revert
seongyun-ko Dec 31, 2024
80c21be
improve the fuzz test
seongyun-ko Dec 31, 2024
4862685
(1) pause the contract on upgrade, (2) prevent from calling 'aggregat…
seongyun-ko Jan 1, 2025
f36c180
only owner of the funds can call {redeemEEth, redeemWeEth}
seongyun-ko Jan 2, 2025
3356771
disable unpause until the scan is completed
seongyun-ko Jan 2, 2025
ddd1b59
check the basis points params are below 1e4
seongyun-ko Jan 2, 2025
43a5a5e
disallow calling 'initializeOnUpgradeWithRedemptionManager' with inva…
seongyun-ko Jan 2, 2025
aa53280
(1) withdrawRequestNFT cannot call LiquidityPool.addEthAmountLockedFo…
seongyun-ko Jan 2, 2025
8ae4844
prevent finalizing future requests
seongyun-ko Jan 2, 2025
26b5a0b
add {redeemEEthWithPermit, redeemWeEthWithPermit}, use try-catch for
seongyun-ko Jan 2, 2025
a785f25
remove a redundant check
seongyun-ko Jan 2, 2025
8785224
Prevent 'initializeOnUpgrade' of LiquidityPool from being called twic…
seongyun-ko Jan 2, 2025
edc30cb
use 'isScanOfShareRemainderCompleted'
seongyun-ko Jan 2, 2025
b666808
add the max value constraint on rate limit
seongyun-ko Jan 2, 2025
23fd48b
remove equality condition
seongyun-ko Jan 2, 2025
7a909ac
fix the overflow issue in '_refill'
seongyun-ko Jan 2, 2025
0e6662b
Update EtherFiRedemptionManager.sol
seongyun-ko Jan 2, 2025
f2cbc82
prevent the total shares from going below 1 gwei after redemption
seongyun-ko Jan 2, 2025
d193b00
fixes for Instant Withdrawal
shivam-ef Jan 16, 2025
17aabaa
fix: certora audit fixes
shivam-ef Jan 20, 2025
c782ab3
changed gas limit in redemption to 50k from 10k
shivam-ef Jan 20, 2025
b877f7e
added only liquidity pool to burn shares
shivam-ef Jan 20, 2025
3128cf3
remove batch cancel deposit by admin since not required
shivam-ef Jan 21, 2025
fdbf21a
Merge branch 'syko/feature/instant_withdrawal' of https://github.com/…
shivam-ef Jan 21, 2025
1cb9971
Merge branch 'master' of https://github.com/etherfi-protocol/smart-co…
shivam-ef Jan 22, 2025
fd0f75f
Merge branch 'syko/feature/instant_withdrawal' of https://github.com/…
shivam-ef Jan 22, 2025
ce827c2
removed duplicate bytecode_hash config from foundry.toml and added au…
shivam-ef Jan 23, 2025
3230fa7
instant withdrawal upgrade prepared
shivam-ef Jan 24, 2025
07bbcfa
instant withdrawal script
shivam-ef Jan 28, 2025
3b4be39
deployed instant withdrawal upgrade
shivam-ef Jan 28, 2025
c2e6a53
fix test
shivam-ef Jan 28, 2025
95cfaaa
Rename 2025.01.23 - Certora - EtherFi - Withdrawal Fee - Re-audit.pdf…
seongyun-ko Feb 6, 2025
824bcf8
Merge pull request #227 from etherfi-protocol/shivam/fix/instant_with…
shivam-ef Feb 10, 2025
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
Binary file not shown.
3 changes: 1 addition & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
src = 'src'
out = 'out'
libs = ['lib']
fs_permissions = [{ access = "read-write", path = "./release"}, { access = "read", path = "./test" }]
fs_permissions = [{ access = "read-write", path = "./release"}, { access = "read", path = "./test" }, { access = "read-write", path = "./operations" }]
gas_reports = ["*"]
optimizer_runs = 2000
extra_output = ["storageLayout"]
bytecode_hash = 'none'
solc-version = '0.8.24'
bytecode_hash = 'none'

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
[rpc_endpoints]
Expand Down
21 changes: 15 additions & 6 deletions lib/BucketLimiter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ library BucketLimiter {
return limit.remaining >= amount;
}

function consumable(Limit memory limit) external view returns (uint64) {
_refill(limit);
return limit.remaining;
}

/*
* Consumes the given amount from the bucket, if there is sufficient capacity, and returns
* whether the bucket had enough remaining capacity to consume the amount.
Expand Down Expand Up @@ -104,18 +109,22 @@ library BucketLimiter {
}

function _refill(Limit memory limit) internal view {
// We allow for overflow here, as the delta is resilient against it.
uint64 now_ = uint64(block.timestamp);
uint64 delta;

if (now_ == limit.lastRefill) {
return;
}

uint256 delta;
unchecked {
delta = now_ - limit.lastRefill;
}
uint64 tokens = delta * limit.refillRate;
uint64 newRemaining = limit.remaining + tokens;
uint256 tokens = delta * uint256(limit.refillRate);
uint256 newRemaining = uint256(limit.remaining) + tokens;
if (newRemaining > limit.capacity) {
limit.remaining = limit.capacity;
} else {
limit.remaining = newRemaining;
limit.remaining = uint64(newRemaining);
}
limit.lastRefill = now_;
}
Expand Down Expand Up @@ -157,4 +166,4 @@ library BucketLimiter {
refill(limit);
limit.remaining = remaining;
}
}
}
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at 8583a6
11 changes: 11 additions & 0 deletions operations/20250128_upgrade_instant_withdrawal_execute.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"chainId": "1",
"meta": { "txBuilderVersion": "1.16.5" },
"transactions": [
{
"to": "0x9f26d4c958fd811a1f59b01b86be7dffc9d20761",
"value": "0",
"data": "0xe38335e500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000012cd17fdc855070e6b92a85ee62b7b5f78b031a423542fabd5d0ace52bdd8aea00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d5706f6ef3f89b3951e23e557cdfbc3239d4e2c000000000000000000000000308861a430be4cce5502d0a12724771fc6daf21600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000c44f1ef28600000000000000000000000078f424c42f006b046b927d49b6d7abef74ca67ae00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000044f8a025b40000000000000000000000009af1298993dc1f397973c62a5d47a284cf76844d0000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a44f1ef286000000000000000000000000a05d566bac16e2fbcec4cff68e89119ee2a96ef900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024984856a3000000000000000000000000d5fd46f4df70a63d60a8563cad0444fcc25dce7f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
]
}
11 changes: 11 additions & 0 deletions operations/20250128_upgrade_instant_withdrawal_schedule.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"chainId": "1",
"meta": { "txBuilderVersion": "1.16.5" },
"transactions": [
{
"to": "0x9f26d4c958fd811a1f59b01b86be7dffc9d20761",
"value": "0",
"data": "0x8f2a0bb000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000012cd17fdc855070e6b92a85ee62b7b5f78b031a423542fabd5d0ace52bdd8aea000000000000000000000000000000000000000000000000000000000003f48000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007d5706f6ef3f89b3951e23e557cdfbc3239d4e2c000000000000000000000000308861a430be4cce5502d0a12724771fc6daf21600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000c44f1ef28600000000000000000000000078f424c42f006b046b927d49b6d7abef74ca67ae00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000044f8a025b40000000000000000000000009af1298993dc1f397973c62a5d47a284cf76844d0000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a44f1ef286000000000000000000000000a05d566bac16e2fbcec4cff68e89119ee2a96ef900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024984856a3000000000000000000000000d5fd46f4df70a63d60a8563cad0444fcc25dce7f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
]
}
84 changes: 84 additions & 0 deletions script/GnosisHelpers.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";

import "@openzeppelin/contracts/utils/Strings.sol";


contract GnosisHelpers is Test {

/**
* @dev Simulations the execution of a gnosis transaction bundle on the current fork
* @param transactionPath The path to the transaction bundle json file
* @param sender The address of the gnosis safe that will execute the transaction
*/
function executeGnosisTransactionBundle(string memory transactionPath, address sender) public {
string memory json = vm.readFile(transactionPath);
for (uint256 i = 0; vm.keyExistsJson(json, string.concat(".transactions[", Strings.toString(i), "]")); i++) {
address to = vm.parseJsonAddress(json, string.concat(string.concat(".transactions[", Strings.toString(i)), "].to"));
uint256 value = vm.parseJsonUint(json, string.concat(string.concat(".transactions[", Strings.toString(i)), "].value"));
bytes memory data = vm.parseJsonBytes(json, string.concat(string.concat(".transactions[", Strings.toString(i)), "].data"));

vm.prank(sender);
(bool success,) = address(to).call{value: value}(data);
require(success, "Transaction failed");
}
}

// Get the gnosis transaction header
function _getGnosisHeader(string memory chainId) internal pure returns (string memory) {
return string.concat('{"chainId":"', chainId, '","meta": { "txBuilderVersion": "1.16.5" }, "transactions": [');
}

// Create a gnosis transaction
// ether sent value is always 0 for our usecase
function _getGnosisTransaction(string memory to, string memory data, bool isLast) internal pure returns (string memory) {
string memory suffix = isLast ? ']}' : ',';
return string.concat('{"to":"', to, '","value":"0","data":"', data, '"}', suffix);
}

// Helper function to convert bytes to hex strings
// soldity encodes returns a bytes object and this must be converted to a hex string to be used in gnosis transactions
function iToHex(bytes memory buffer) public pure returns (string memory) {
// Fixed buffer size for hexadecimal convertion
bytes memory converted = new bytes(buffer.length * 2);

bytes memory _base = "0123456789abcdef";

for (uint256 i = 0; i < buffer.length; i++) {
converted[i * 2] = _base[uint8(buffer[i]) / _base.length];
converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length];
}

return string(abi.encodePacked("0x", converted));
}

// Helper function to convert an address to a hex string of the bytes
function addressToHex(address addr) public pure returns (string memory) {
return iToHex(abi.encodePacked(addr));
}

address public timelock = 0x9f26d4C958fD811A1F59B01B86Be7dFFc9d20761;
bytes32 public predecessor = 0x0000000000000000000000000000000000000000000000000000000000000000;
bytes32 public salt = 0x0000000000000000000000000000000000000000000000000000000000000000;
uint256 public delay = 259200;

// Generates the schedule transaction for a gnosis safe
function _getGnosisScheduleTransaction(address to, bytes memory data, bool isLasts) internal view returns (string memory) {

string memory timelockAddressHex = iToHex(abi.encodePacked(address(timelock)));
string memory scheduleTransactionData = iToHex(abi.encodeWithSignature("schedule(address,uint256,bytes,bytes32,bytes32,uint256)", to, 0, data, predecessor, salt, delay));

return _getGnosisTransaction(timelockAddressHex, scheduleTransactionData, isLasts);
}

function _getGnosisExecuteTransaction(address to, bytes memory data, bool isLasts) internal view returns (string memory) {

string memory timelockAddressHex = iToHex(abi.encodePacked(address(timelock)));
string memory executeTransactionData = iToHex(abi.encodeWithSignature("execute(address,uint256,bytes,bytes32,bytes32)", to, 0, data, predecessor, salt));

return _getGnosisTransaction(timelockAddressHex, executeTransactionData, isLasts);
}

}
42 changes: 42 additions & 0 deletions script/deploys/DeployEtherFiRestaker.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Script.sol";

import "../../src/Liquifier.sol";
import "../../src/EtherFiRestaker.sol";
import "../../src/helpers/AddressProvider.sol";
import "../../src/UUPSProxy.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract Deploy is Script {
using Strings for string;

UUPSProxy public liquifierProxy;

Liquifier public liquifierInstance;

AddressProvider public addressProvider;

address admin;

function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address addressProviderAddress = vm.envAddress("CONTRACT_REGISTRY");
addressProvider = AddressProvider(addressProviderAddress);

vm.startBroadcast(deployerPrivateKey);

EtherFiRestaker restaker = EtherFiRestaker(payable(new UUPSProxy(payable(new EtherFiRestaker()), "")));
restaker.initialize(
addressProvider.getContractAddress("LiquidityPool"),
addressProvider.getContractAddress("Liquifier")
);

new Liquifier();

// addressProvider.addContract(address(liquifierInstance), "Liquifier");

vm.stopBroadcast();
}
}
40 changes: 40 additions & 0 deletions script/deploys/DeployEtherFiWithdrawalBuffer.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Script.sol";

import "@openzeppelin/contracts/utils/Strings.sol";

import "../../src/Liquifier.sol";
import "../../src/EtherFiRestaker.sol";
import "../../src/helpers/AddressProvider.sol";
import "../../src/UUPSProxy.sol";
import "../../src/EtherFiRedemptionManager.sol";


contract Deploy is Script {
using Strings for string;
AddressProvider public addressProvider;

function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address addressProviderAddress = vm.envAddress("CONTRACT_REGISTRY");
addressProvider = AddressProvider(addressProviderAddress);

vm.startBroadcast(deployerPrivateKey);

EtherFiRedemptionManager impl = new EtherFiRedemptionManager(
addressProvider.getContractAddress("LiquidityPool"),
addressProvider.getContractAddress("EETH"),
addressProvider.getContractAddress("WeETH"),
0x0c83EAe1FE72c390A02E426572854931EefF93BA, // protocol safe
0x1d3Af47C1607A2EF33033693A9989D1d1013BB50 // role registry
);
UUPSProxy proxy = new UUPSProxy(payable(impl), "");

EtherFiRedemptionManager instance = EtherFiRedemptionManager(payable(proxy));
instance.initialize(10_00, 1_00, 1_00, 5 ether, 0.001 ether);

vm.stopBroadcast();
}
}
2 changes: 1 addition & 1 deletion script/deploys/DeployPhaseTwo.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ contract DeployPhaseTwoScript is Script {
}
retrieve_contract_addresses();

withdrawRequestNftImplementation = new WithdrawRequestNFT();
withdrawRequestNftImplementation = new WithdrawRequestNFT(address(0));
withdrawRequestNftProxy = new UUPSProxy(address(withdrawRequestNftImplementation), "");
withdrawRequestNftInstance = WithdrawRequestNFT(payable(withdrawRequestNftProxy));

Expand Down
Loading
Loading