-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the scripting for deploying the lockup contract
- Loading branch information
Showing
6 changed files
with
262 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { viem } from 'hardhat'; | ||
import type { Address } from 'viem'; | ||
|
||
import { walletFromKey } from './generateWallets'; | ||
|
||
export async function deployWeeklyVesting({ ScoutERC20Address }: { ScoutERC20Address: Address }) { | ||
const sablierAdminAccount = await walletFromKey(); | ||
|
||
const SablierNFTDescriptor = await viem.deployContract('SablierV2NFTDescriptor', []); | ||
|
||
const SablierLockupTranched = await viem.deployContract('SablierV2LockupTranched', [ | ||
sablierAdminAccount.account.address, | ||
SablierNFTDescriptor.address, | ||
BigInt(52) | ||
]); | ||
|
||
const WeeklyERC20Vesting = await viem.deployContract('LockupWeeklyStreamCreator', [ | ||
ScoutERC20Address, | ||
SablierLockupTranched.address | ||
]); | ||
|
||
// Return the proxy with the implementation ABI attached | ||
return { | ||
WeeklyERC20Vesting, | ||
SablierLockupTranched, | ||
SablierNFTDescriptor | ||
}; | ||
} | ||
|
||
export type WeeklyVestingTestFixture = Awaited<ReturnType<typeof deployWeeklyVesting>>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { mine, time } from '@nomicfoundation/hardhat-network-helpers'; | ||
import { parseEventLogs } from 'viem'; | ||
|
||
import { prettyPrint } from '../../lib/prettyPrint'; | ||
import type { ProtocolERC20TestFixture } from '../deployScoutTokenERC20'; | ||
import { deployScoutTokenERC20 } from '../deployScoutTokenERC20'; | ||
import { deployWeeklyVesting, type WeeklyVestingTestFixture } from '../deployWeeklyVesting'; | ||
import type { GeneratedWallet } from '../generateWallets'; | ||
import { walletFromKey } from '../generateWallets'; | ||
|
||
describe('deployScoutTokenERC20', () => { | ||
let weeklyVesting: WeeklyVestingTestFixture; | ||
let ProtocolERC20: ProtocolERC20TestFixture; | ||
let streamCreator: GeneratedWallet; | ||
let recipient: GeneratedWallet; | ||
|
||
beforeAll(async () => { | ||
ProtocolERC20 = await deployScoutTokenERC20(); | ||
weeklyVesting = await deployWeeklyVesting({ | ||
ScoutERC20Address: ProtocolERC20.ProtocolERC20.address | ||
}); | ||
recipient = await walletFromKey(); | ||
streamCreator = await walletFromKey(); | ||
}); | ||
|
||
it('should deploy the weekly vesting contract, with functional vesting', async () => { | ||
const { SablierLockupTranched, WeeklyERC20Vesting } = weeklyVesting; | ||
|
||
const { ProtocolERC20AdminAccount } = ProtocolERC20; | ||
|
||
// Fund the stream creator with some tokens | ||
await ProtocolERC20.transferProtocolERC20({ | ||
args: { to: streamCreator.account.address, amount: 20_000 }, | ||
wallet: ProtocolERC20AdminAccount | ||
}); | ||
|
||
const amountToVest = 10_000; | ||
|
||
const totalWeeks = 10; | ||
|
||
// Approve the contract | ||
await ProtocolERC20.approveProtocolERC20({ | ||
args: { spender: WeeklyERC20Vesting.address, amount: amountToVest }, | ||
wallet: streamCreator | ||
}); | ||
|
||
const receipt = await WeeklyERC20Vesting.write.createStream( | ||
[ | ||
recipient.account.address, | ||
BigInt(amountToVest) * ProtocolERC20.ProtocolERC20_DECIMAL_MULTIPLIER, | ||
BigInt(totalWeeks) | ||
], | ||
{ | ||
account: streamCreator.account | ||
} | ||
); | ||
|
||
const output = await streamCreator.getTransactionReceipt({ hash: receipt }); | ||
|
||
const streamLog = parseEventLogs({ | ||
abi: SablierLockupTranched.abi, | ||
logs: output.logs, | ||
eventName: ['CreateLockupTranchedStream'] | ||
})[0].args; | ||
|
||
const recipientErc20Balance = await ProtocolERC20.balanceOfProtocolERC20({ | ||
account: recipient.account.address | ||
}); | ||
|
||
expect(recipientErc20Balance).toBe(0); | ||
|
||
await time.setNextBlockTimestamp(streamLog.tranches[0].timestamp); | ||
|
||
await mine(); | ||
|
||
const current = await time.latest(); | ||
|
||
console.log({ current }); | ||
|
||
const withdrawReceipt = await SablierLockupTranched.write.withdrawMax( | ||
[streamLog.streamId, recipient.account.address], | ||
{ | ||
account: recipient.account | ||
} | ||
); | ||
|
||
const withdrawOutput = await recipient.getTransactionReceipt({ hash: withdrawReceipt }); | ||
|
||
const withdrawLog = parseEventLogs({ | ||
abi: SablierLockupTranched.abi, | ||
logs: withdrawOutput.logs, | ||
eventName: ['WithdrawFromLockupStream'] | ||
})[0].args; | ||
|
||
const recipientErc20BalanceAfter = await ProtocolERC20.balanceOfProtocolERC20({ | ||
account: recipient.account.address | ||
}); | ||
|
||
prettyPrint({ | ||
recipientErc20BalanceAfter, | ||
amountToVest, | ||
streamLog, | ||
withdrawLog | ||
}); | ||
|
||
expect(recipientErc20BalanceAfter).toBe(amountToVest / totalWeeks); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity >=0.8.22; | ||
|
||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {ud60x18} from "@prb/math/src/UD60x18.sol"; | ||
import {ud60x18} from "@prb/math/src/UD60x18.sol"; | ||
import {ISablierV2LockupTranched} from "@sablier/v2-core/src/interfaces/ISablierV2LockupTranched.sol"; | ||
import {Broker, LockupTranched} from "@sablier/v2-core/src/types/DataTypes.sol"; | ||
|
||
// Import here so that it is compiled and included in the hardhat artifacts | ||
import {SablierV2NFTDescriptor} from "@sablier/v2-core/src/SablierV2NFTDescriptor.sol"; | ||
import {SablierV2LockupTranched} from "@sablier/v2-core/src/SablierV2LockupTranched.sol"; | ||
|
||
/// @notice Example of how to create a Lockup Linear stream. | ||
/// @dev This code is referenced in the docs: https://docs.sablier.com/contracts/v2/guides/create-stream/lockup-linear | ||
contract LockupWeeklyStreamCreator { | ||
// sepolia addresses | ||
IERC20 public SCOUT; | ||
|
||
ISablierV2LockupTranched public LOCKUP_TRANCHED; | ||
|
||
constructor(address _erc20, address _lockupTranched) { | ||
SCOUT = IERC20(_erc20); | ||
LOCKUP_TRANCHED = ISablierV2LockupTranched(_lockupTranched); | ||
} | ||
|
||
/// @dev For this function to work, the sender must have approved this dummy contract to spend SCOUT. | ||
function createStream( | ||
address recipient, | ||
uint128 totalAmount, | ||
uint128 _weeks | ||
) public returns (uint256 streamId) { | ||
// Transfer the provided amount of SCOUT tokens to this contract | ||
SCOUT.transferFrom(msg.sender, address(this), totalAmount); | ||
|
||
// Approve the Sablier contract to spend SCOUT | ||
SCOUT.approve(address(LOCKUP_TRANCHED), totalAmount); | ||
|
||
// Declare the params struct | ||
LockupTranched.CreateWithDurations memory params; | ||
|
||
// Declare the function parameters | ||
params.sender = msg.sender; // The sender will be able to cancel the stream | ||
params.recipient = address(recipient); // The recipient of the streamed assets | ||
params.totalAmount = uint128(totalAmount); // Total amount is the amount inclusive of all fees | ||
params.asset = SCOUT; // The streaming asset | ||
params.cancelable = true; // Whether the stream will be cancelable or not | ||
params.transferable = true; // Whether the stream will be transferable or not | ||
|
||
// Declare some dummy tranches | ||
params.tranches = new LockupTranched.TrancheWithDuration[](_weeks); | ||
|
||
uint128 _streamed = 0; | ||
|
||
uint128 _amountPerWeek = totalAmount / _weeks; | ||
|
||
for (uint256 i = 0; i < _weeks; i++) { | ||
if (i != _weeks - 1) { | ||
params.tranches[i] = LockupTranched.TrancheWithDuration({ | ||
amount: _amountPerWeek, | ||
duration: 1 weeks | ||
}); | ||
|
||
_streamed += _amountPerWeek; | ||
} else { | ||
uint128 _remainder = totalAmount - _streamed; | ||
params.tranches[i] = LockupTranched.TrancheWithDuration({ | ||
amount: _remainder, | ||
duration: 1 weeks | ||
}); | ||
|
||
_streamed += _remainder; | ||
} | ||
} | ||
|
||
// Wondering what's up with that ud60x18 function? It's a casting function that wraps a basic integer to the UD60x18 value type. This type is part of the math library PRBMath, which is used in Sablier for fixed-point calculations. | ||
params.broker = Broker(address(0), ud60x18(0)); // Optional parameter left undefined | ||
|
||
// Create the LockupTranched stream | ||
streamId = LOCKUP_TRANCHED.createWithDurations(params); | ||
} | ||
|
||
function _weeksToSeconds(uint256 _weeks) internal pure returns (uint256) { | ||
return _weeks * 60 * 60 * 24 * 7; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## List of Sablier deployments | ||
https://docs.sablier.com/contracts/v2/deployments#base | ||
https://docs.sablier.com/contracts/v2/deployments#base-sepolia |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters