Compare commits
2 Commits
6998355887
...
379cc331e2
| Author | SHA1 | Date | |
|---|---|---|---|
| 379cc331e2 | |||
| 15e93e7f32 |
@ -104,18 +104,12 @@ GOVERNOR_VOTING_PERIOD=
|
|||||||
GOVERNOR_PROPOSAL_THRESHOLD=
|
GOVERNOR_PROPOSAL_THRESHOLD=
|
||||||
GOVERNOR_QUORUM_FRACTION=
|
GOVERNOR_QUORUM_FRACTION=
|
||||||
|
|
||||||
###################### Initial ghosted supply on gatekeeper ##########################
|
###################### Initial ghosted supply on gatekeeper ###########################
|
||||||
## ghostedSupply - supply that is currently locked inside the gatekeeper ##
|
## existential - minimum amount that could be reflected inside other chain ##
|
||||||
## existential - minimum amount that could be reflected inside other chain ##
|
## previousWeaver - previous weaver address if any to make linked list of weavers ##
|
||||||
## ghostedIn - historical supply that was bridged in (max value is 2^104) ##
|
#######################################################################################
|
||||||
## ghostedOut - historical supply that was bridged out (max value is 2^104) ##
|
|
||||||
## deployedAt - time when gatekeepers was deployed, could be inherited if needed ##
|
|
||||||
######################################################################################
|
|
||||||
INITIAL_GHOSTED_SUPPLY=
|
|
||||||
INITIAL_EXISTENTIAL_DEPOSIT=
|
INITIAL_EXISTENTIAL_DEPOSIT=
|
||||||
INITIAL_GHOSTED_IN=
|
PREVIOUS_WEAVER_ADDRESS=
|
||||||
INITIAL_GHOSTED_OUT=
|
|
||||||
GATEKEEPER_DEPLOYED_AT=
|
|
||||||
|
|
||||||
SEPOLIA_TEST_RPC_URL=
|
SEPOLIA_TEST_RPC_URL=
|
||||||
SEPOLIA_TEST_API_KEY=
|
SEPOLIA_TEST_API_KEY=
|
||||||
|
|||||||
@ -10,22 +10,24 @@ contract Gatekeeper is IGatekeeper, Metadata, Weaver {
|
|||||||
|
|
||||||
uint256 public override existentialDeposit;
|
uint256 public override existentialDeposit;
|
||||||
uint256 public override ghostedSupply;
|
uint256 public override ghostedSupply;
|
||||||
address public immutable staking; // forge-lint: disable-line(screaming-snake-case-immutable)
|
address public override staking;
|
||||||
|
|
||||||
uint256 private _aggregatedPublicKey;
|
uint256 private _aggregatedPublicKey;
|
||||||
mapping(uint256 => mapping(uint256 => bool)) private _executedTransaction;
|
mapping(uint256 => mapping(uint256 => bool)) private _executedTransaction;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
address _staking,
|
address _staking,
|
||||||
uint256 _ghostedSupply,
|
uint256 _existentialDeposit,
|
||||||
uint256 _existential,
|
address _previousGatekeeper
|
||||||
uint48 _deployedAt,
|
) Metadata(_previousGatekeeper) Weaver(_previousGatekeeper) {
|
||||||
uint104 _amountIn,
|
existentialDeposit = _existentialDeposit;
|
||||||
uint104 _amountOut
|
if (_previousGatekeeper != address(0)) {
|
||||||
) Metadata(_deployedAt, _amountIn, _amountOut) Weaver() {
|
IGatekeeper previousGatekeeper = IGatekeeper(_previousGatekeeper);
|
||||||
existentialDeposit = _existential;
|
ghostedSupply = previousGatekeeper.ghostedSupply();
|
||||||
ghostedSupply = _ghostedSupply;
|
staking = previousGatekeeper.staking();
|
||||||
staking = _staking;
|
} else {
|
||||||
|
staking = _staking;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ghost(bytes32 receiver, uint256 amount) external override returns (uint256) {
|
function ghost(bytes32 receiver, uint256 amount) external override returns (uint256) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ interface IGatekeeper {
|
|||||||
|
|
||||||
function ghostedSupply() external view returns (uint256);
|
function ghostedSupply() external view returns (uint256);
|
||||||
function existentialDeposit() external view returns (uint256);
|
function existentialDeposit() external view returns (uint256);
|
||||||
|
function staking() external view returns (address);
|
||||||
function ghost(bytes32 receiver, uint256 amount) external returns (uint256);
|
function ghost(bytes32 receiver, uint256 amount) external returns (uint256);
|
||||||
function materialize(address receiver, uint256 amount, uint256 rx, uint256 s) external;
|
function materialize(address receiver, uint256 amount, uint256 rx, uint256 s) external;
|
||||||
function rotate(uint256 aggregatedPublicKey, uint256 rx, uint256 s) external;
|
function rotate(uint256 aggregatedPublicKey, uint256 rx, uint256 s) external;
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
pragma solidity ^0.8.20;
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
interface IWeaver {
|
interface IWeaver {
|
||||||
function getSlotValues(uint256 globalIndex, uint256 session, uint256 atBlock) external returns (bytes32[] memory);
|
function currentSession() external view returns (uint256);
|
||||||
function getProof(uint256 globalIndex, uint256 session, uint256 atBlock) external returns (bytes32[] memory);
|
function startSession() external view returns (uint256);
|
||||||
function getRoot(uint256 session, uint256 atBlock) external returns (bytes32, uint256);
|
function getSlotValues(uint256 globalIndex, uint256 session, uint256 atBlock) external view returns (bytes32[] memory);
|
||||||
|
function getProof(uint256 globalIndex, uint256 session, uint256 atBlock) external view returns (bytes32[] memory);
|
||||||
|
function getRoot(uint256 session, uint256 atBlock) external view returns (bytes32, uint256);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ pragma solidity ^0.8.20;
|
|||||||
import {Weaver} from "../types/Weaver.sol";
|
import {Weaver} from "../types/Weaver.sol";
|
||||||
|
|
||||||
contract WeaverMock is Weaver {
|
contract WeaverMock is Weaver {
|
||||||
constructor() Weaver() {}
|
constructor(address previousWeaver) Weaver(previousWeaver) {}
|
||||||
|
|
||||||
function insertTreeNode(bytes32 receiver, uint256 amount) external returns (uint256) {
|
function insertTreeNode(bytes32 receiver, uint256 amount) external returns (uint256) {
|
||||||
return _insertTreeNode(receiver, amount);
|
return _insertTreeNode(receiver, amount);
|
||||||
|
|||||||
@ -6,16 +6,21 @@ import {IMetadata} from "../interfaces/IMetadata.sol";
|
|||||||
abstract contract Metadata is IMetadata {
|
abstract contract Metadata is IMetadata {
|
||||||
GatekeeperMetadata internal _metadata;
|
GatekeeperMetadata internal _metadata;
|
||||||
|
|
||||||
constructor(
|
constructor(address previosMetadata) {
|
||||||
uint48 _deployedAt,
|
if (previosMetadata == address(0)) {
|
||||||
uint104 _amountIn,
|
_metadata = GatekeeperMetadata({
|
||||||
uint104 _amountOut
|
deployedAt: uint48(block.number),
|
||||||
) {
|
amountIn: 0,
|
||||||
_metadata = GatekeeperMetadata({
|
amountOut: 0
|
||||||
deployedAt: _deployedAt,
|
});
|
||||||
amountIn: _amountIn,
|
} else {
|
||||||
amountOut: _amountOut
|
IMetadata.GatekeeperMetadata memory previousMetadata = IMetadata(previosMetadata).metadata();
|
||||||
});
|
_metadata = GatekeeperMetadata({
|
||||||
|
deployedAt: previousMetadata.deployedAt,
|
||||||
|
amountIn: previousMetadata.amountIn,
|
||||||
|
amountOut: previousMetadata.amountOut
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function metadata() external view returns (GatekeeperMetadata memory) {
|
function metadata() external view returns (GatekeeperMetadata memory) {
|
||||||
|
|||||||
@ -14,20 +14,33 @@ abstract contract Weaver is IWeaver {
|
|||||||
uint256 public constant SLOTS = 2 ** DEPTH;
|
uint256 public constant SLOTS = 2 ** DEPTH;
|
||||||
uint256 public constant ENTRIES = DEPTH * SLOTS;
|
uint256 public constant ENTRIES = DEPTH * SLOTS;
|
||||||
|
|
||||||
uint256 public currentSession;
|
uint256 public override currentSession;
|
||||||
|
uint256 public override startSession;
|
||||||
|
address public previousWeaver;
|
||||||
|
|
||||||
mapping(uint256 => mapping(uint256 => Checkpoints.Trace256)) internal _treeNodes;
|
mapping(uint256 => mapping(uint256 => Checkpoints.Trace256)) internal _treeNodes;
|
||||||
mapping(uint256 => mapping(uint256 => Checkpoints.Trace160)) internal _slotLenghts;
|
mapping(uint256 => mapping(uint256 => Checkpoints.Trace160)) internal _slotLenghts;
|
||||||
mapping(uint256 => mapping(uint256 => bytes32[])) internal _slotValues;
|
mapping(uint256 => mapping(uint256 => bytes32[])) internal _slotValues;
|
||||||
mapping(uint256 => uint256) internal _filledEntries;
|
mapping(uint256 => uint256) internal _filledEntries;
|
||||||
|
|
||||||
constructor() {}
|
constructor(address _previousWeaver) {
|
||||||
|
previousWeaver = _previousWeaver;
|
||||||
|
if (_previousWeaver != address(0)) {
|
||||||
|
uint256 newSession = IWeaver(_previousWeaver).currentSession() + 1;
|
||||||
|
currentSession = newSession;
|
||||||
|
startSession = newSession;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getSlotValues(
|
function getSlotValues(
|
||||||
uint256 globalIndex,
|
uint256 globalIndex,
|
||||||
uint256 session,
|
uint256 session,
|
||||||
uint256 atBlock
|
uint256 atBlock
|
||||||
) external view returns (bytes32[] memory) {
|
) external override view returns (bytes32[] memory) {
|
||||||
|
if (session < startSession) {
|
||||||
|
return IWeaver(previousWeaver).getSlotValues(globalIndex, session, atBlock);
|
||||||
|
}
|
||||||
|
|
||||||
uint256 slotIndex = globalIndex % SLOTS;
|
uint256 slotIndex = globalIndex % SLOTS;
|
||||||
// forge-lint: disable-next-line(unsafe-typecast)
|
// forge-lint: disable-next-line(unsafe-typecast)
|
||||||
uint256 length = _slotLenghts[session][slotIndex].upperLookupRecent(uint96(atBlock));
|
uint256 length = _slotLenghts[session][slotIndex].upperLookupRecent(uint96(atBlock));
|
||||||
@ -42,7 +55,11 @@ abstract contract Weaver is IWeaver {
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRoot(uint256 session, uint256 atBlock) public view returns (bytes32, uint256) {
|
function getRoot(uint256 session, uint256 atBlock) public override view returns (bytes32, uint256) {
|
||||||
|
if (session < startSession) {
|
||||||
|
return IWeaver(previousWeaver).getRoot(session, atBlock);
|
||||||
|
}
|
||||||
|
|
||||||
uint256 currentLevelCount = SLOTS >> 1;
|
uint256 currentLevelCount = SLOTS >> 1;
|
||||||
bytes32[] memory currentLevel = new bytes32[](currentLevelCount);
|
bytes32[] memory currentLevel = new bytes32[](currentLevelCount);
|
||||||
|
|
||||||
@ -83,7 +100,11 @@ abstract contract Weaver is IWeaver {
|
|||||||
uint256 globalIndex,
|
uint256 globalIndex,
|
||||||
uint256 session,
|
uint256 session,
|
||||||
uint256 atBlock
|
uint256 atBlock
|
||||||
) external view returns (bytes32[] memory) {
|
) external override view returns (bytes32[] memory) {
|
||||||
|
if (session < startSession) {
|
||||||
|
return IWeaver(previousWeaver).getProof(globalIndex, session, atBlock);
|
||||||
|
}
|
||||||
|
|
||||||
uint256 currentLevelCount = SLOTS;
|
uint256 currentLevelCount = SLOTS;
|
||||||
uint256 currentIndex = globalIndex % SLOTS;
|
uint256 currentIndex = globalIndex % SLOTS;
|
||||||
|
|
||||||
|
|||||||
@ -324,7 +324,7 @@ contract GhostBondDepositoryTest is Test {
|
|||||||
uint256 amount = 10_000 * 1e18; // 10,000
|
uint256 amount = 10_000 * 1e18; // 10,000
|
||||||
|
|
||||||
vm.startPrank(GOVERNOR);
|
vm.startPrank(GOVERNOR);
|
||||||
Gatekeeper gatekeeper = new Gatekeeper(address(staking), 0, 0, 0, 0, 0);
|
Gatekeeper gatekeeper = new Gatekeeper(address(staking), 0, address(0));
|
||||||
staking.setGatekeeperAddress(address(gatekeeper));
|
staking.setGatekeeperAddress(address(gatekeeper));
|
||||||
staking.setWarmupPeriod(1);
|
staking.setWarmupPeriod(1);
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
@ -509,7 +509,7 @@ contract GhostBondDepositoryTest is Test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vm.startPrank(GOVERNOR);
|
vm.startPrank(GOVERNOR);
|
||||||
Gatekeeper gatekeeper = new Gatekeeper(address(staking), 0, 0, 0, 0, 0);
|
Gatekeeper gatekeeper = new Gatekeeper(address(staking), 0, address(0));
|
||||||
staking.setGatekeeperAddress(address(gatekeeper));
|
staking.setGatekeeperAddress(address(gatekeeper));
|
||||||
staking.setWarmupPeriod(10);
|
staking.setWarmupPeriod(10);
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
|
|||||||
@ -14,18 +14,21 @@ contract GatekeeperTest is Test {
|
|||||||
event Ghosted(bytes32 indexed receiver, uint256 indexed amount);
|
event Ghosted(bytes32 indexed receiver, uint256 indexed amount);
|
||||||
|
|
||||||
function setUp() public {
|
function setUp() public {
|
||||||
gatekeeper = new Gatekeeper(ALICE, 0, EXISTENTIAL, 0, 0, 0);
|
gatekeeper = new Gatekeeper(ALICE, EXISTENTIAL, address(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_correctInitialization() public {
|
function test_correctInitialization() public {
|
||||||
assertEq(gatekeeper.staking(), ALICE);
|
assertEq(gatekeeper.staking(), ALICE);
|
||||||
assertEq(gatekeeper.ghostedSupply(), 0);
|
assertEq(gatekeeper.ghostedSupply(), 0);
|
||||||
|
|
||||||
Gatekeeper anotherGatekeeper = new Gatekeeper(BOB, INIT_AMOUNT, EXISTENTIAL, 0, 0, 0);
|
bytes32 receiver = bytes32(abi.encodePacked(ALICE));
|
||||||
assertEq(anotherGatekeeper.staking(), BOB);
|
vm.prank(ALICE);
|
||||||
|
gatekeeper.ghost(receiver, INIT_AMOUNT);
|
||||||
|
|
||||||
|
Gatekeeper anotherGatekeeper = new Gatekeeper(BOB, EXISTENTIAL, address(gatekeeper));
|
||||||
|
assertEq(anotherGatekeeper.staking(), ALICE);
|
||||||
assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT);
|
assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT);
|
||||||
assertEq(anotherGatekeeper.existentialDeposit(), EXISTENTIAL);
|
assertEq(anotherGatekeeper.existentialDeposit(), EXISTENTIAL);
|
||||||
assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_ghostTokensWork(uint256 ghostAmount) public {
|
function test_ghostTokensWork(uint256 ghostAmount) public {
|
||||||
|
|||||||
@ -9,22 +9,19 @@ contract GatekeeperMetadataTest is Test {
|
|||||||
uint256 constant INIT_AMOUNT = 69 * 1e18;
|
uint256 constant INIT_AMOUNT = 69 * 1e18;
|
||||||
uint256 constant INIT_GHOSTED = type(uint104).max / 2;
|
uint256 constant INIT_GHOSTED = type(uint104).max / 2;
|
||||||
uint256 constant EXISTENTIAL = 0;
|
uint256 constant EXISTENTIAL = 0;
|
||||||
uint48 constant DEPLOYED_AT = 1337;
|
|
||||||
uint104 constant AMOUNT_IN = 69;
|
|
||||||
uint104 constant AMOUNT_OUT = 420;
|
|
||||||
|
|
||||||
Gatekeeper gatekeeper;
|
Gatekeeper gatekeeper;
|
||||||
|
|
||||||
function setUp() public {
|
function setUp() public {
|
||||||
gatekeeper = new Gatekeeper(ALICE, INIT_GHOSTED, EXISTENTIAL, DEPLOYED_AT, AMOUNT_IN, AMOUNT_OUT);
|
gatekeeper = new Gatekeeper(ALICE, EXISTENTIAL, address(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_correctMetadataInitialization() public view {
|
function test_correctMetadataInitialization() public view {
|
||||||
Gatekeeper.GatekeeperMetadata memory metadata = gatekeeper.metadata();
|
Gatekeeper.GatekeeperMetadata memory metadata = gatekeeper.metadata();
|
||||||
assertEq(metadata.deployedAt, DEPLOYED_AT);
|
assertEq(metadata.deployedAt, block.number);
|
||||||
assertEq(metadata.amountIn, AMOUNT_IN);
|
assertEq(metadata.amountIn, 0);
|
||||||
assertEq(metadata.amountOut, AMOUNT_OUT);
|
assertEq(metadata.amountOut, 0);
|
||||||
assertEq(gatekeeper.ghostedSupply(), INIT_GHOSTED);
|
assertEq(gatekeeper.ghostedSupply(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_historicalAmountsOnlyIncrease(uint256 ghostAmount) public {
|
function test_historicalAmountsOnlyIncrease(uint256 ghostAmount) public {
|
||||||
|
|||||||
@ -12,18 +12,12 @@ contract GatekeeperVerification is Gatekeeper {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
address staking,
|
address staking,
|
||||||
uint256 ghostedSupply,
|
|
||||||
uint256 existential,
|
uint256 existential,
|
||||||
uint48 deployedAt,
|
address previousWeaver
|
||||||
uint104 amountIn,
|
|
||||||
uint104 amountOut
|
|
||||||
) Gatekeeper(
|
) Gatekeeper(
|
||||||
staking,
|
staking,
|
||||||
ghostedSupply,
|
|
||||||
existential,
|
existential,
|
||||||
deployedAt,
|
previousWeaver
|
||||||
amountIn,
|
|
||||||
amountOut
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
function filledEntries(uint256 session) public view returns (uint256) {
|
function filledEntries(uint256 session) public view returns (uint256) {
|
||||||
@ -102,7 +96,7 @@ contract GatekeeperWeaverTest is Test {
|
|||||||
GatekeeperVerification gatekeeper;
|
GatekeeperVerification gatekeeper;
|
||||||
|
|
||||||
function setUp() public {
|
function setUp() public {
|
||||||
gatekeeper = new GatekeeperVerification(ALICE, 0, EXISTENTIAL, 0, 0, 0);
|
gatekeeper = new GatekeeperVerification(ALICE, EXISTENTIAL, address(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_insertationWorksAsExpected() public {
|
function test_insertationWorksAsExpected() public {
|
||||||
@ -138,6 +132,35 @@ contract GatekeeperWeaverTest is Test {
|
|||||||
assertFalse(_verifyProof(globalIndex, newSession, 100, amounts[69], whos[69]));
|
assertFalse(_verifyProof(globalIndex, newSession, 100, amounts[69], whos[69]));
|
||||||
assertFalse(_verifyProof(1337, currentSession, 100, amounts[1337], whos[1337]));
|
assertFalse(_verifyProof(1337, currentSession, 100, amounts[1337], whos[1337]));
|
||||||
assertFalse(_verifyProof(2047, currentSession, 100, amounts[2047], whos[2047]));
|
assertFalse(_verifyProof(2047, currentSession, 100, amounts[2047], whos[2047]));
|
||||||
|
|
||||||
|
vm.roll(block.number + 420);
|
||||||
|
gatekeeper = new GatekeeperVerification(ALICE, EXISTENTIAL, address(gatekeeper));
|
||||||
|
uint256 finalSession = gatekeeper.currentSession();
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < 69; i++) {
|
||||||
|
if (i % 2 == 0) { vm.roll(block.number + 1); }
|
||||||
|
vm.prank(ALICE);
|
||||||
|
gatekeeper.ghost(whos[i], amounts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(_verifyProof(globalIndex, newSession, block.number, amounts[69], whos[69]));
|
||||||
|
assertTrue(_verifyProof(0, currentSession, block.number, amounts[0], whos[0]));
|
||||||
|
assertTrue(_verifyProof(69, currentSession, block.number, amounts[69], whos[69]));
|
||||||
|
assertTrue(_verifyProof(420, currentSession, block.number, amounts[420], whos[420]));
|
||||||
|
assertTrue(_verifyProof(1337, currentSession, block.number, amounts[1337], whos[1337]));
|
||||||
|
assertTrue(_verifyProof(2047, currentSession, block.number, amounts[2047], whos[2047]));
|
||||||
|
|
||||||
|
assertTrue(_verifyProof(0, currentSession, 100, amounts[0], whos[0]));
|
||||||
|
assertTrue(_verifyProof(69, currentSession, 100, amounts[69], whos[69]));
|
||||||
|
assertTrue(_verifyProof(420, currentSession, 100, amounts[420], whos[420]));
|
||||||
|
|
||||||
|
assertFalse(_verifyProof(globalIndex, newSession, 100, amounts[69], whos[69]));
|
||||||
|
assertFalse(_verifyProof(1337, currentSession, 100, amounts[1337], whos[1337]));
|
||||||
|
assertFalse(_verifyProof(2047, currentSession, 100, amounts[2047], whos[2047]));
|
||||||
|
|
||||||
|
assertTrue(_verifyProof(0, finalSession, block.number, amounts[0], whos[0]));
|
||||||
|
assertTrue(_verifyProof(34, finalSession, block.number, amounts[34], whos[34]));
|
||||||
|
assertTrue(_verifyProof(35, finalSession, block.number, amounts[35], whos[35]));
|
||||||
}
|
}
|
||||||
|
|
||||||
function _verifyProof(
|
function _verifyProof(
|
||||||
|
|||||||
@ -110,7 +110,7 @@ contract StakingTest is Test {
|
|||||||
treasury = new GhostTreasury(address(ftso), 69, address(authority));
|
treasury = new GhostTreasury(address(ftso), 69, address(authority));
|
||||||
stnk.initialize(address(staking), address(treasury), address(ghst));
|
stnk.initialize(address(staking), address(treasury), address(ghst));
|
||||||
ghst.initialize(address(staking));
|
ghst.initialize(address(staking));
|
||||||
gatekeeper = new Gatekeeper(address(staking), 0, 0, 0, 0, 0);
|
gatekeeper = new Gatekeeper(address(staking), 0, address(0));
|
||||||
calculator = new GhostBondingCalculator(address(ftso), 1, 1);
|
calculator = new GhostBondingCalculator(address(ftso), 1, 1);
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
vm.roll(block.number + 1);
|
vm.roll(block.number + 1);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user