Skip to content

Commit

Permalink
feat: split v2.1 (#55)
Browse files Browse the repository at this point in the history
* feat(splitv2.1): use forceApprove and other nit changes

* feat(splitv2.1): store last updated block number

* fix: lint

* test: add usdt distribution test on mainnet fork

* chore: add missing env to gh action

* fix: update doc

* feat: pausable deposit to warehouse

* feat: update v2.1 deployment script

* fix: update wallet version
  • Loading branch information
r0ohafza authored Dec 23, 2024
1 parent 581a5b4 commit 3c7d2c6
Show file tree
Hide file tree
Showing 21 changed files with 150 additions and 55 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:

env:
FOUNDRY_PROFILE: ci
MAINNET_RPC_URL: ${{ vars.MAINNET_RPC_URL }}

jobs:
check:
Expand Down
2 changes: 1 addition & 1 deletion packages/smart-vaults/deployments/1.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"SmartVaultFactory": "0x8E6Af8Ed94E87B4402D0272C5D6b0D47F0483e7C"
}
}
2 changes: 1 addition & 1 deletion packages/smart-vaults/deployments/10.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"SmartVaultFactory": "0x8E6Af8Ed94E87B4402D0272C5D6b0D47F0483e7C"
}
}
2 changes: 1 addition & 1 deletion packages/smart-vaults/deployments/11155111.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"SmartVaultFactory": "0x8E6Af8Ed94E87B4402D0272C5D6b0D47F0483e7C"
}
}
2 changes: 1 addition & 1 deletion packages/smart-vaults/deployments/11155420.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"SmartVaultFactory": "0x8E6Af8Ed94E87B4402D0272C5D6b0D47F0483e7C"
}
}
2 changes: 1 addition & 1 deletion packages/smart-vaults/deployments/7777777.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"SmartVaultFactory": "0x8E6Af8Ed94E87B4402D0272C5D6b0D47F0483e7C"
}
}
2 changes: 1 addition & 1 deletion packages/smart-vaults/deployments/8453.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"SmartVaultFactory": "0x8E6Af8Ed94E87B4402D0272C5D6b0D47F0483e7C"
}
}
2 changes: 1 addition & 1 deletion packages/smart-vaults/deployments/84532.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"SmartVaultFactory": "0x8E6Af8Ed94E87B4402D0272C5D6b0D47F0483e7C"
}
}
2 changes: 1 addition & 1 deletion packages/smart-vaults/deployments/999999999.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"SmartVaultFactory": "0x8E6Af8Ed94E87B4402D0272C5D6b0D47F0483e7C"
}
}
12 changes: 7 additions & 5 deletions packages/splits-v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,15 @@ For a test run, use the following command:

To deploy split v2 contracts on a given chain, please make sure the following requirements are met:

1. Send the minimum amount of native gas token for deployment to the deployer address: `0x60C65c9a8674DA22e89C7d09e839908B9f0ecC3a`. Mainnet deployment transactions for gas cost:
1. Send the minimum amount of native gas token for deployment to the deployer address:
`0x60C65c9a8674DA22e89C7d09e839908B9f0ecC3a`. Mainnet deployment transactions for gas cost:

* [Warehouse](https://etherscan.io/tx/0x9a24df13332fafff979c35d5475be6a0594b9e8a632b1ff603150c413b7c134c)
* [Pull Splits](https://etherscan.io/tx/0xe81eb2677e597ae98c65558487693d94494e28387f2a9d76782992e4f399f44a)
* [Push Splits](https://etherscan.io/tx/0x20e8da208491560c658a25dcaa2bf37f94f26ccb4d5caaac4a346b2152818513)
- [Warehouse](https://etherscan.io/tx/0x9a24df13332fafff979c35d5475be6a0594b9e8a632b1ff603150c413b7c134c)
- [Pull Splits](https://etherscan.io/tx/0xe81eb2677e597ae98c65558487693d94494e28387f2a9d76782992e4f399f44a)
- [Push Splits](https://etherscan.io/tx/0x20e8da208491560c658a25dcaa2bf37f94f26ccb4d5caaac4a346b2152818513)

2. Support for [CreateX](https://createx.rocks/). We use createX as our deployer factory. This will ensure that the addresses match existing deployments.
2. Support for [CreateX](https://createx.rocks/). We use createX as our deployer factory. This will ensure that the
addresses match existing deployments.

3. Complete OP Code compatibility with evm version: `Shanghai`.

Expand Down
2 changes: 1 addition & 1 deletion packages/splits-v2/deployments/360.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"PullSplitFactory": "0x80f1B766817D04870f115fEBbcCADF8DBF75E017",
"PushSplitFactory": "0xaDC87646f736d6A82e9a6539cddC488b2aA07f38",
"SplitsWarehouse": "0x8fb66F38cF86A3d5e8768f8F1754A24A6c661Fb8"
}
}
20 changes: 6 additions & 14 deletions packages/splits-v2/script/SplitFactoryV2.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,17 @@ import { PushSplitFactory } from "../src/splitters/push/PushSplitFactory.sol";
import { BaseScript } from "./Base.s.sol";

contract SplitFactoryV2Script is BaseScript {
uint88 private constant PUSH_DEPLOYMENT_SALT = 1;
uint88 private constant PULL_DEPLOYMENT_SALT = 2;

function run() public {
address warehouse = getAddressFromConfig("splitsWarehouse");

bytes memory args = abi.encode(warehouse);

address deployer = vm.envAddress("DEPLOYER");

bytes32 pull_salt = computeSalt(deployer, bytes11(PULL_DEPLOYMENT_SALT));
bytes32 push_salt = computeSalt(deployer, bytes11(PUSH_DEPLOYMENT_SALT));

vm.startBroadcast();
address pull_factory = create3(pull_salt, abi.encodePacked(type(PullSplitFactory).creationCode, args));
address push_factory = create3(push_salt, abi.encodePacked(type(PushSplitFactory).creationCode, args));
address pull_factory =
address(new PullSplitFactory{ salt: keccak256("splits.pullSplitFactory.v2.1") }(warehouse));
address push_factory =
address(new PushSplitFactory{ salt: keccak256("splits.pushSplitFactory.v2.1") }(warehouse));
vm.stopBroadcast();

updateDeployment(pull_factory, "PullSplitFactory");
updateDeployment(push_factory, "PushSplitFactory");
updateDeployment(pull_factory, "PullSplitFactoryV2.1");
updateDeployment(push_factory, "PushSplitFactoryV2.1");
}
}
6 changes: 3 additions & 3 deletions packages/splits-v2/src/SplitsWarehouse.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortSt
* @title Splits Token Warehouse
* @author Splits
* @notice ERC6909 compliant token warehouse for Splits ecosystem
* @dev Token id here is address(uint160(uint256 id)).
* @dev Token id here is uint256(uint160(address tokenAddress)).
*/
contract SplitsWarehouse is ERC6909X {
using Cast for uint256;
Expand All @@ -29,7 +29,6 @@ contract SplitsWarehouse is ERC6909X {

error InvalidAmount();
error LengthMismatch();
error ZeroOwner();
error WithdrawalPaused(address owner);

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -210,6 +209,7 @@ contract SplitsWarehouse is ERC6909X {
* @param _token The address of the token to be withdrawn.
*/
function withdraw(address _owner, address _token) external {
// solhint-disable-next-line avoid-tx-origin
if (msg.sender != _owner && tx.origin != _owner) {
if (withdrawConfig[_owner].paused) {
revert WithdrawalPaused(_owner);
Expand Down Expand Up @@ -262,8 +262,8 @@ contract SplitsWarehouse is ERC6909X {

/**
* @notice Batch transfers tokens to the specified addresses from msg.sender.
* @param _token The address of the token to be transferred.
* @param _receivers The addresses of the receivers.
* @param _token The address of the token to be transferred.
* @param _amounts The amounts of the tokens to be transferred.
*/
function batchTransfer(address[] calldata _receivers, address _token, uint256[] calldata _amounts) external {
Expand Down
22 changes: 17 additions & 5 deletions packages/splits-v2/src/splitters/SplitFactoryV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ abstract contract SplitFactoryV2 is Nonces {
/* EVENTS */
/* -------------------------------------------------------------------------- */

event SplitCreated(
address indexed split, SplitV2Lib.Split splitParams, address owner, address creator, bytes32 salt
);

event SplitCreated(address indexed split, SplitV2Lib.Split splitParams, address owner, address creator);

/* -------------------------------------------------------------------------- */
Expand All @@ -32,8 +36,7 @@ abstract contract SplitFactoryV2 is Nonces {

/**
* @notice Create a new split using create2.
* @dev if integrating, please make sure you understand how to handle greifing
* properly to avoid potential issues with frontrunning. See docs for more information.
* @dev Returns existing split if already created, otherwise creates new split.
* @param _splitParams Params to create split with.
* @param _owner Owner of created split.
* @param _creator Creator of created split.
Expand All @@ -48,14 +51,23 @@ abstract contract SplitFactoryV2 is Nonces {
external
returns (address split)
{
split = Clone.cloneDeterministic({
bytes32 salt = _getSalt({ _splitParams: _splitParams, _owner: _owner, _salt: _salt });

split = Clone.predictDeterministicAddress({
_implementation: SPLIT_WALLET_IMPLEMENTATION,
_salt: _getSalt({ _splitParams: _splitParams, _owner: _owner, _salt: _salt })
_salt: salt,
_deployer: address(this)
});

if (split.code.length > 0) {
return split;
}

split = Clone.cloneDeterministic({ _implementation: SPLIT_WALLET_IMPLEMENTATION, _salt: salt });

SplitWalletV2(split).initialize(_splitParams, _owner);

emit SplitCreated({ split: split, splitParams: _splitParams, owner: _owner, creator: _creator });
emit SplitCreated({ split: split, splitParams: _splitParams, owner: _owner, creator: _creator, salt: _salt });
}

/**
Expand Down
11 changes: 7 additions & 4 deletions packages/splits-v2/src/splitters/SplitWalletV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,14 @@ abstract contract SplitWalletV2 is Wallet, ERC1271 {
/// @notice the split hash - Keccak256 hash of the split struct
bytes32 public splitHash;

/// @notice the block number at which the split was last updated
uint256 public updateBlockNumber;

/* -------------------------------------------------------------------------- */
/* CONSTRUCTOR & INITIALIZER */
/* -------------------------------------------------------------------------- */

constructor(address _splitWarehouse) ERC1271("splitWallet", "2") {
constructor(address _splitWarehouse) ERC1271("splitWallet", "2.1") {
SPLITS_WAREHOUSE = ISplitsWarehouse(_splitWarehouse);
NATIVE_TOKEN = SPLITS_WAREHOUSE.NATIVE_TOKEN();
FACTORY = msg.sender;
Expand All @@ -73,8 +76,9 @@ abstract contract SplitWalletV2 is Wallet, ERC1271 {
if (msg.sender != FACTORY) revert UnauthorizedInitializer();

_split.validate();

splitHash = _split.getHash();
updateBlockNumber = block.number;
emit SplitUpdated(_split);

Wallet.__initWallet(_owner);
}
Expand Down Expand Up @@ -115,9 +119,8 @@ abstract contract SplitWalletV2 is Wallet, ERC1271 {
function updateSplit(SplitV2Lib.Split calldata _split) external onlyOwner {
// throws error if invalid
_split.validate();

splitHash = _split.getHash();

updateBlockNumber = block.number;
emit SplitUpdated(_split);
}

Expand Down
28 changes: 17 additions & 11 deletions packages/splits-v2/src/splitters/pull/PullSplit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ contract PullSplit is SplitWalletV2 {
(uint256 splitBalance, uint256 warehouseBalance) = getSplitBalance(_token);

// @solidity memory-safe-assembly
// solhint-disable-next-line no-inline-assembly
assembly {
// splitBalance -= uint(splitBalance > 0);
splitBalance := sub(splitBalance, iszero(iszero(splitBalance)))
// warehouseBalance -= uint(warehouseBalance > 0);
warehouseBalance := sub(warehouseBalance, iszero(iszero(warehouseBalance)))
}

if (splitBalance > 0) depositToWarehouse(_token, splitBalance);
if (splitBalance > 0) _depositToWarehouse(_token, splitBalance);

_distribute({
_split: _split,
Expand Down Expand Up @@ -93,7 +94,7 @@ contract PullSplit is SplitWalletV2 {
if (_performWarehouseTransfer) {
uint256 amount =
(_token == NATIVE_TOKEN ? address(this).balance : IERC20(_token).balanceOf(address(this))) - 1;
depositToWarehouse(_token, amount);
_depositToWarehouse(_token, amount);
}

_distribute({ _split: _split, _token: _token, _amount: _distributeAmount, _distributor: _distributor });
Expand All @@ -104,22 +105,27 @@ contract PullSplit is SplitWalletV2 {
* @param _token The token to deposit.
* @param _amount The amount of tokens to deposit
*/
function depositToWarehouse(address _token, uint256 _amount) public {
function depositToWarehouse(address _token, uint256 _amount) external pausable {
_depositToWarehouse(_token, _amount);
}

/* -------------------------------------------------------------------------- */
/* INTERNAL/PRIVATE */
/* -------------------------------------------------------------------------- */

function _depositToWarehouse(address _token, uint256 _amount) internal {
if (_token == NATIVE_TOKEN) {
SPLITS_WAREHOUSE.deposit{ value: _amount }({ receiver: address(this), token: _token, amount: _amount });
} else {
// solhint-disable-next-line no-empty-blocks
try SPLITS_WAREHOUSE.deposit({ receiver: address(this), token: _token, amount: _amount }) { }
catch {
IERC20(_token).approve({ spender: address(SPLITS_WAREHOUSE), amount: type(uint256).max });
IERC20(_token).forceApprove({ spender: address(SPLITS_WAREHOUSE), value: type(uint256).max });
SPLITS_WAREHOUSE.deposit({ receiver: address(this), token: _token, amount: _amount });
}
}
}

/* -------------------------------------------------------------------------- */
/* INTERNAL/PRIVATE */
/* -------------------------------------------------------------------------- */

/// @dev Assumes the amount is already deposited to the warehouse.
function _distribute(
SplitV2Lib.Split calldata _split,
Expand All @@ -129,12 +135,12 @@ contract PullSplit is SplitWalletV2 {
)
internal
{
(uint256[] memory amounts, uint256 distibutorReward) = _split.getDistributions(_amount);
(uint256[] memory amounts, uint256 distributorReward) = _split.getDistributions(_amount);

SPLITS_WAREHOUSE.batchTransfer({ receivers: _split.recipients, token: _token, amounts: amounts });

if (distibutorReward > 0) {
SPLITS_WAREHOUSE.transfer({ receiver: _distributor, id: _token.toUint256(), amount: distibutorReward });
if (distributorReward > 0) {
SPLITS_WAREHOUSE.transfer({ receiver: _distributor, id: _token.toUint256(), amount: distributorReward });
}

emit SplitDistributed({ token: _token, distributor: _distributor, amount: _amount });
Expand Down
1 change: 1 addition & 0 deletions packages/splits-v2/src/splitters/push/PushSplit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ contract PushSplit is SplitWalletV2 {
if (warehouseBalance > 1) withdrawFromWarehouse(_token);

// @solidity memory-safe-assembly
// solhint-disable-next-line no-inline-assembly
assembly {
// splitBalance -= uint(splitBalance > 0);
splitBalance := sub(splitBalance, iszero(iszero(splitBalance)))
Expand Down
Loading

0 comments on commit 3c7d2c6

Please sign in to comment.