Skip to content

Commit

Permalink
feat: combine zaps with onboarding approval
Browse files Browse the repository at this point in the history
  • Loading branch information
amarinkovic committed Nov 6, 2024
1 parent a34ec81 commit bba825d
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 51 deletions.
9 changes: 4 additions & 5 deletions src/facets/AdminFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity 0.8.20;

import { AppStorage, LibAppStorage } from "../shared/AppStorage.sol";
import { OnboardingApproval } from "../shared/FreeStructs.sol";
import { Modifiers } from "../shared/Modifiers.sol";
import { LibAdmin } from "../libs/LibAdmin.sol";
import { LibObject } from "../libs/LibObject.sol";
Expand Down Expand Up @@ -149,12 +150,10 @@ contract AdminFacet is Modifiers {

/**
* @notice Create a token holder entity for a user account
* @param _entityId object ID for which the fee schedule is being set, use system ID for global fee schedule
* @param _roleId Role to assign to the entity
* @param _sig Signature approving the user to be onboarded to the given role
* @param _onboardingApproval onboarding approval parameters, includes user address, entity ID and role ID
*/
function onboardViaSignature(bytes32 _entityId, bytes32 _roleId, bytes calldata _sig) external {
LibAdmin._onboardUserViaSignature(msg.sender, _entityId, _roleId, _sig);
function onboardViaSignature(OnboardingApproval calldata _onboardingApproval) external {
LibAdmin._onboardUserViaSignature(_onboardingApproval);
}

/**
Expand Down
32 changes: 19 additions & 13 deletions src/facets/ZapFacet.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import { PermitSignature } from "../shared/FreeStructs.sol";
import { PermitSignature, OnboardingApproval } from "../shared/FreeStructs.sol";
import { Modifiers } from "../shared/Modifiers.sol";
import { LibTokenizedVaultIO } from "../libs/LibTokenizedVaultIO.sol";
import { LibEntity } from "../libs/LibEntity.sol";
Expand All @@ -18,24 +18,28 @@ contract ZapFacet is Modifiers, ReentrancyGuard {
* @notice Deposit and stake funds into msg.sender's Nayms platform entity in one transaction using permit
* @dev Uses permit to approve token transfer, deposits from msg.sender to their associated entity, and stakes the amount
* @param _externalTokenAddress Token address
* @param _entityId Staking entity ID
* @param _stakingEntityId Staking entity ID
* @param _amountToDeposit Deposit amount
* @param _amountToStake Stake amount
* @param _permitSignature The permit signature parameters
* @param _onboardingApproval The onboarding approval parameters
*/
function zapStake(
address _externalTokenAddress,
bytes32 _entityId,
bytes32 _stakingEntityId,
uint256 _amountToDeposit,
uint256 _amountToStake,
PermitSignature calldata _permitSignature
PermitSignature calldata _permitSignature,
OnboardingApproval calldata _onboardingApproval
) external notLocked nonReentrant {
// Check if it's a supported ERC20 token
require(LibAdmin._isSupportedExternalTokenAddress(_externalTokenAddress), "zapStake: invalid ERC20 token");

// Get the user's entity
bytes32 parentId = LibObject._getParentFromAddress(msg.sender);
require(LibEntity._isEntity(parentId), "zapStake: invalid receiver");

if (!LibEntity._isEntity(parentId)) {
LibAdmin._onboardUserViaSignature(_onboardingApproval);
}

// Use permit to set allowance
IERC20(_externalTokenAddress).permit(msg.sender, address(this), _amountToDeposit, _permitSignature.deadline, _permitSignature.v, _permitSignature.r, _permitSignature.s);
Expand All @@ -44,7 +48,7 @@ contract ZapFacet is Modifiers, ReentrancyGuard {
LibTokenizedVaultIO._externalDeposit(parentId, _externalTokenAddress, _amountToDeposit);

// Stake the deposited amount
LibTokenizedVaultStaking._stake(parentId, _entityId, _amountToStake);
LibTokenizedVaultStaking._stake(parentId, _stakingEntityId, _amountToStake);
}

/**
Expand All @@ -57,6 +61,7 @@ contract ZapFacet is Modifiers, ReentrancyGuard {
* @param _buyToken Buy token ID
* @param _buyAmount Buy amount
* @param _permitSignature The permit signature parameters
* @param _onboardingApproval The onboarding approval parameters
* @return offerId_ The ID of the created offer
* @return buyTokenCommissionsPaid_ Commissions paid in buy token
* @return sellTokenCommissionsPaid_ Commissions paid in sell token
Expand All @@ -68,7 +73,8 @@ contract ZapFacet is Modifiers, ReentrancyGuard {
uint256 _sellAmount,
bytes32 _buyToken,
uint256 _buyAmount,
PermitSignature calldata _permitSignature
PermitSignature calldata _permitSignature,
OnboardingApproval calldata _onboardingApproval
)
external
notLocked
Expand All @@ -79,17 +85,17 @@ contract ZapFacet is Modifiers, ReentrancyGuard {
// Check if it's a supported ERC20 token
require(LibAdmin._isSupportedExternalTokenAddress(_externalTokenAddress), "zapOrder: invalid ERC20 token");

// Get the user's entity
bytes32 parentId = LibObject._getParentFromAddress(msg.sender);
require(LibEntity._isEntity(parentId), "zapOrder: invalid entity");
if (!LibEntity._isEntity(LibObject._getParentFromAddress(msg.sender))) {
LibAdmin._onboardUserViaSignature(_onboardingApproval);
}

// Use permit to set allowance
IERC20(_externalTokenAddress).permit(msg.sender, address(this), _depositAmount, _permitSignature.deadline, _permitSignature.v, _permitSignature.r, _permitSignature.s);

// Perform the external deposit
LibTokenizedVaultIO._externalDeposit(parentId, _externalTokenAddress, _depositAmount);
LibTokenizedVaultIO._externalDeposit(LibObject._getParentFromAddress(msg.sender), _externalTokenAddress, _depositAmount);

// Execute the limit order
return LibMarket._executeLimitOffer(parentId, _sellToken, _sellAmount, _buyToken, _buyAmount, LC.FEE_TYPE_TRADING);
return LibMarket._executeLimitOffer(LibObject._getParentFromAddress(msg.sender), _sellToken, _sellAmount, _buyToken, _buyAmount, LC.FEE_TYPE_TRADING);
}
}
42 changes: 24 additions & 18 deletions src/libs/LibAdmin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity 0.8.20;

import { AppStorage, FunctionLockedStorage, LibAppStorage } from "../shared/AppStorage.sol";
import { Entity, EntityApproval } from "../shared/FreeStructs.sol";
import { Entity, OnboardingApproval } from "../shared/FreeStructs.sol";
import { LibConstants as LC } from "./LibConstants.sol";
import { LibHelpers } from "./LibHelpers.sol";
import { LibObject } from "./LibObject.sol";
Expand Down Expand Up @@ -228,42 +228,48 @@ library LibAdmin {
emit FunctionsUnlocked(lockedFunctions);
}

function _onboardUserViaSignature(address _userAddress, bytes32 _entityId, bytes32 _roleId, bytes calldata sig) internal {
function _onboardUserViaSignature(OnboardingApproval memory _approval) internal {
AppStorage storage s = LibAppStorage.diamondStorage();

if (_entityId == 0 || _roleId == 0 || sig.length == 0) revert EntityOnboardingNotApproved(_userAddress);
address userAddress = msg.sender;

bool isTokenHolder = _roleId == LibHelpers._stringToBytes32(LC.ROLE_ENTITY_TOKEN_HOLDER);
bool isCapitalProvider = _roleId == LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP);
bytes32 entityId = _approval.entityId;
bytes32 roleId = _approval.roleId;
bytes memory sig = _approval.signature;

if (entityId == 0 || roleId == 0 || sig.length == 0) revert EntityOnboardingNotApproved(userAddress);

bool isTokenHolder = roleId == LibHelpers._stringToBytes32(LC.ROLE_ENTITY_TOKEN_HOLDER);
bool isCapitalProvider = roleId == LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP);
if (!isTokenHolder && !isCapitalProvider) {
revert InvalidSelfOnboardRoleApproval(_roleId);
revert InvalidSelfOnboardRoleApproval(roleId);
}

bytes32 signingHash = _getOnboardingHash(_userAddress, _entityId, _roleId);
bytes32 signingHash = _getOnboardingHash(userAddress, entityId, roleId);
bytes32 signerId = LibHelpers._getIdForAddress(_getSigner(signingHash, sig));

if (!LibACL._isInGroup(signerId, LibAdmin._getSystemId(), LibHelpers._stringToBytes32(LC.GROUP_ONBOARDING_APPROVERS))) {
revert EntityOnboardingNotApproved(_userAddress);
revert EntityOnboardingNotApproved(userAddress);
}

if (!s.existingEntities[_entityId]) {
if (!s.existingEntities[entityId]) {
Entity memory entity;
bytes32 userId = LibHelpers._getIdForAddress(_userAddress);
LibEntity._createEntity(_entityId, userId, entity, 0);
bytes32 userId = LibHelpers._getIdForAddress(userAddress);
LibEntity._createEntity(entityId, userId, entity, 0);
}

if (s.roles[_entityId][_entityId] != 0) {
LibACL._unassignRole(_entityId, _entityId);
if (s.roles[entityId][entityId] != 0) {
LibACL._unassignRole(entityId, entityId);
}

if (s.roles[_entityId][LibAdmin._getSystemId()] != 0) {
LibACL._unassignRole(_entityId, LibAdmin._getSystemId());
if (s.roles[entityId][LibAdmin._getSystemId()] != 0) {
LibACL._unassignRole(entityId, LibAdmin._getSystemId());
}

LibACL._assignRole(_entityId, LibAdmin._getSystemId(), _roleId);
LibACL._assignRole(_entityId, _entityId, _roleId);
LibACL._assignRole(entityId, LibAdmin._getSystemId(), roleId);
LibACL._assignRole(entityId, entityId, roleId);

emit SelfOnboardingCompleted(_userAddress);
emit SelfOnboardingCompleted(userAddress);
}

function _setMinimumSell(bytes32 _objectId, uint256 _minimumSell) internal {
Expand Down
7 changes: 7 additions & 0 deletions src/shared/FreeStructs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct Entity {
bool simplePolicyEnabled;
}

// DEPRECATED, but don't remove, referenced in appstorage
struct EntityApproval {
bytes32 entityId;
bytes32 roleId;
Expand Down Expand Up @@ -133,3 +134,9 @@ struct PermitSignature {
bytes32 r;
bytes32 s;
}

struct OnboardingApproval {
bytes32 entityId;
bytes32 roleId;
bytes signature;
}
20 changes: 10 additions & 10 deletions test/T04Entity.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.20;
import { Vm } from "forge-std/Vm.sol";

import { c, D03ProtocolDefaults, LibHelpers, LC } from "./defaults/D03ProtocolDefaults.sol";
import { Entity, MarketInfo, SimplePolicy, SimplePolicyInfo, Stakeholders } from "src/shared/FreeStructs.sol";
import { Entity, MarketInfo, SimplePolicy, SimplePolicyInfo, Stakeholders, OnboardingApproval } from "src/shared/FreeStructs.sol";
import { IDiamondCut } from "lib/diamond-2-hardhat/contracts/interfaces/IDiamondCut.sol";
import { StdStyle } from "forge-std/StdStyle.sol";

Expand Down Expand Up @@ -1174,15 +1174,15 @@ contract T04EntityTest is D03ProtocolDefaults {

vm.startPrank(userAddress);
vm.expectRevert(abi.encodeWithSelector(EntityOnboardingNotApproved.selector, userAddress));
nayms.onboardViaSignature(entityId, roleId, noSig);
nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: roleId, signature: noSig }));

bytes memory sig = signWithPK(em.pk, nayms.getOnboardingHash(userAddress, entityId, roleId));

vm.expectRevert(abi.encodeWithSelector(EntityOnboardingNotApproved.selector, userAddress));
nayms.onboardViaSignature(0x0, roleId, sig);
nayms.onboardViaSignature(OnboardingApproval({ entityId: 0x0, roleId: roleId, signature: sig }));

vm.expectRevert(abi.encodeWithSelector(EntityOnboardingNotApproved.selector, userAddress));
nayms.onboardViaSignature(entityId, 0x0, sig);
nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: 0x0, signature: sig }));
vm.stopPrank();

vm.startPrank(sm.addr);
Expand All @@ -1191,7 +1191,7 @@ contract T04EntityTest is D03ProtocolDefaults {

vm.startPrank(userAddress);
vm.expectRevert(abi.encodeWithSelector(EntityOnboardingNotApproved.selector, userAddress));
nayms.onboardViaSignature(entityId, roleId, sig);
nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: roleId, signature: sig }));
}

function testSelfOnboardingInvalidGroup() public {
Expand All @@ -1205,7 +1205,7 @@ contract T04EntityTest is D03ProtocolDefaults {

vm.startPrank(userAddress);
vm.expectRevert(abi.encodeWithSelector(InvalidSelfOnboardRoleApproval.selector, sysMgrRoleId));
nayms.onboardViaSignature(entityId, sysMgrRoleId, sig);
nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: sysMgrRoleId, signature: sig }));
vm.stopPrank();
}

Expand All @@ -1219,7 +1219,7 @@ contract T04EntityTest is D03ProtocolDefaults {
bytes memory sig = signWithPK(em.pk, nayms.getOnboardingHash(userAddress, entityId, roleId));

vm.startPrank(userAddress);
nayms.onboardViaSignature(entityId, roleId, sig);
nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: roleId, signature: sig }));
vm.stopPrank();

assertEq(nayms.getEntity(LibHelpers._getIdForAddress(userAddress)), entityId, "parent should be set");
Expand All @@ -1241,11 +1241,11 @@ contract T04EntityTest is D03ProtocolDefaults {
bytes memory sigCapitalProvider = signWithPK(em.pk, nayms.getOnboardingHash(userAddress, e1, roleIdCapitalProvider));

vm.startPrank(userAddress);
nayms.onboardViaSignature(e1, roleIdTokenHolder, sigTokenHolder);
nayms.onboardViaSignature(OnboardingApproval({ entityId: e1, roleId: roleIdTokenHolder, signature: sigTokenHolder }));

vm.recordLogs();

nayms.onboardViaSignature(e1, roleIdCapitalProvider, sigCapitalProvider);
nayms.onboardViaSignature(OnboardingApproval({ entityId: e1, roleId: roleIdCapitalProvider, signature: sigCapitalProvider }));

Vm.Log[] memory entries = vm.getRecordedLogs();

Expand All @@ -1266,7 +1266,7 @@ contract T04EntityTest is D03ProtocolDefaults {

vm.startPrank(userAddress);
vm.expectRevert(abi.encodeWithSelector(InvalidObjectType.selector, entityId, LC.OBJECT_TYPE_ENTITY));
nayms.onboardViaSignature(entityId, roleId, sig);
nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: roleId, signature: sig }));
vm.stopPrank();
}

Expand Down
13 changes: 8 additions & 5 deletions test/T07Zaps.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.20;

import { D03ProtocolDefaults, c, LC, LibHelpers, StdStyle } from "./defaults/D03ProtocolDefaults.sol";
import { DummyToken } from "test/utils/DummyToken.sol";
import { StakingConfig, PermitSignature } from "src/shared/FreeStructs.sol";
import { StakingConfig, PermitSignature, OnboardingApproval } from "src/shared/FreeStructs.sol";

contract ZapFacetTest is D03ProtocolDefaults {
using LibHelpers for address;
Expand Down Expand Up @@ -71,11 +71,12 @@ contract ZapFacetTest is D03ProtocolDefaults {
startPrank(bob);

PermitSignature memory permitSignature = PermitSignature({ deadline: deadline, v: v, r: r, s: s });
OnboardingApproval memory approval;

vm.expectRevert("zapStake: invalid ERC20 token");
nayms.zapStake(address(111), nlf.entityId, stakeAmount, stakeAmount, permitSignature);
nayms.zapStake(address(111), nlf.entityId, stakeAmount, stakeAmount, permitSignature, approval);

nayms.zapStake(address(naymToken), nlf.entityId, stakeAmount, stakeAmount, permitSignature);
nayms.zapStake(address(naymToken), nlf.entityId, stakeAmount, stakeAmount, permitSignature, approval);

(uint256 staked, ) = nayms.getStakingAmounts(bob.entityId, nlf.entityId);

Expand All @@ -99,16 +100,18 @@ contract ZapFacetTest is D03ProtocolDefaults {
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", weth.DOMAIN_SEPARATOR(), structHash));
// Sign the digest
(uint8 v, bytes32 r, bytes32 s) = vm.sign(bob.pk, digest);

PermitSignature memory permitSignature = PermitSignature({ deadline: deadline, v: v, r: r, s: s });
OnboardingApproval memory approval;

startPrank(bob);

vm.expectRevert("zapOrder: invalid ERC20 token");
nayms.zapOrder(address(111), 10 ether, wethId, 1 ether, bob.entityId, 1 ether, permitSignature);
nayms.zapOrder(address(111), 10 ether, wethId, 1 ether, bob.entityId, 1 ether, permitSignature, approval);

// Call zapOrder
// Caller should ensure they deposit enough to cover order fees.
nayms.zapOrder(address(weth), 10 ether, wethId, 1 ether, bob.entityId, 1 ether, permitSignature);
nayms.zapOrder(address(weth), 10 ether, wethId, 1 ether, bob.entityId, 1 ether, permitSignature, approval);

assertEq(nayms.internalBalanceOf(bob.entityId, bob.entityId), 1 ether, "bob should've purchased 1e18 bob p tokens");
}
Expand Down

0 comments on commit bba825d

Please sign in to comment.