Skip to content

Commit 5ed872c

Browse files
committed
use delegate cash
1 parent 6980293 commit 5ed872c

5 files changed

Lines changed: 479 additions & 3 deletions

File tree

integration/integration.test.ts

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3618,5 +3618,246 @@ describe("VolBidPool", function () {
36183618
BigNumber.from(optionTokenId)
36193619
);
36203620
});
3621+
3622+
it("works with property-validator encoded orders", async function () {
3623+
const blockNumber = await ethers.provider.getBlockNumber();
3624+
const block = await ethers.provider.getBlock(blockNumber);
3625+
const blockTimestamp = block.timestamp;
3626+
const expiration = Math.floor(blockTimestamp + SECS_IN_A_DAY * 5);
3627+
3628+
// Mint call option
3629+
const createCall = await calls
3630+
.connect(writer)
3631+
.mintWithErc721(token.address, 0, 1000, expiration);
3632+
const cc = await createCall.wait();
3633+
3634+
const callCreatedEvent = cc.events.find(
3635+
(event: any) => event?.event === "CallCreated"
3636+
);
3637+
3638+
const optionTokenId = callCreatedEvent.args.optionId;
3639+
3640+
const validatorFactory = await ethers.getContractFactory(
3641+
"PropertyValidator1"
3642+
);
3643+
3644+
const validator = await validatorFactory.deploy();
3645+
3646+
const volOrder = {
3647+
direction: OrderDirection.BUY,
3648+
maker: buyer.address,
3649+
orderExpiry: Math.floor(blockTimestamp + 40).toString(),
3650+
nonce: "1405",
3651+
size: "1",
3652+
optionType: OptionType.CALL,
3653+
maxStrikePriceMultiple: "0",
3654+
minOptionDuration: (SECS_IN_A_DAY * 0.5).toString(),
3655+
maxOptionDuration: (SECS_IN_A_DAY * 80).toString(),
3656+
maxPriceSignalAge: "0",
3657+
optionMarketAddress: calls.address,
3658+
impliedVolBips: "5000",
3659+
nftProperties: [
3660+
{
3661+
propertyValidator: validator.address,
3662+
propertyData: ethers.utils.defaultAbiCoder.encode(
3663+
[
3664+
"uint256",
3665+
"uint8",
3666+
"uint256",
3667+
"uint8",
3668+
"bool",
3669+
"uint256",
3670+
"uint256",
3671+
],
3672+
[0, 0, 0, 0, false, 0, 0]
3673+
),
3674+
},
3675+
],
3676+
skewDecimal: "0",
3677+
riskFreeRateBips: "500",
3678+
};
3679+
3680+
const signedOrder = await signVolOrder(volOrder, buyer, protocol.address);
3681+
3682+
const { types, domain, value } = genVolOrderTypedData(
3683+
volOrder,
3684+
protocol.address
3685+
);
3686+
3687+
const orderHash = _TypedDataEncoder.hash(domain, types, value);
3688+
3689+
const orderValiditySignature = await admin.signMessage(
3690+
ethers.utils.arrayify(
3691+
ethers.utils.solidityKeccak256(
3692+
["bytes32", "uint256"],
3693+
[orderHash, expiration]
3694+
)
3695+
)
3696+
);
3697+
3698+
const { v, r, s } = ethers.utils.splitSignature(orderValiditySignature);
3699+
3700+
const orderValidityClaim = {
3701+
orderHash: orderHash,
3702+
goodTilTimestamp: expiration,
3703+
v,
3704+
r,
3705+
s,
3706+
};
3707+
3708+
const {
3709+
v: v2,
3710+
r: r2,
3711+
s: s2,
3712+
} = ethers.utils.splitSignature(
3713+
await admin.signMessage(
3714+
ethers.utils.arrayify(
3715+
ethers.utils.solidityKeccak256(
3716+
["uint256", "uint256", "uint256"],
3717+
["900", Math.floor(blockTimestamp - 10).toString(), expiration]
3718+
)
3719+
)
3720+
)
3721+
);
3722+
3723+
const assetPriceClaim = {
3724+
assetPriceInWei: "900",
3725+
priceObservedTimestamp: Math.floor(blockTimestamp - 10).toString(),
3726+
goodTilTimestamp: expiration,
3727+
v: v2,
3728+
r: r2,
3729+
s: s2,
3730+
};
3731+
3732+
calls.connect(writer).setApprovalForAll(bidPool.address, true);
3733+
weth.connect(buyer).deposit({ value: 1000 });
3734+
weth.connect(buyer).approve(bidPool.address, 1000);
3735+
bidPool
3736+
.connect(writer)
3737+
.sellOption(
3738+
volOrder,
3739+
signedOrder,
3740+
assetPriceClaim,
3741+
orderValidityClaim,
3742+
BigNumber.from("14"),
3743+
calls.address,
3744+
BigNumber.from(optionTokenId)
3745+
);
3746+
});
3747+
3748+
it("works with property-validator empty validator", async function () {
3749+
const blockNumber = await ethers.provider.getBlockNumber();
3750+
const block = await ethers.provider.getBlock(blockNumber);
3751+
const blockTimestamp = block.timestamp;
3752+
const expiration = Math.floor(blockTimestamp + SECS_IN_A_DAY * 5);
3753+
3754+
// Mint call option
3755+
const createCall = await calls
3756+
.connect(writer)
3757+
.mintWithErc721(token.address, 0, 1000, expiration);
3758+
const cc = await createCall.wait();
3759+
3760+
const callCreatedEvent = cc.events.find(
3761+
(event: any) => event?.event === "CallCreated"
3762+
);
3763+
3764+
const optionTokenId = callCreatedEvent.args.optionId;
3765+
3766+
const validatorFactory = await ethers.getContractFactory(
3767+
"PropertyValidator1"
3768+
);
3769+
3770+
const validator = await validatorFactory.deploy();
3771+
3772+
const volOrder = {
3773+
direction: OrderDirection.BUY,
3774+
maker: buyer.address,
3775+
orderExpiry: Math.floor(blockTimestamp + 40).toString(),
3776+
nonce: "1405",
3777+
size: "1",
3778+
optionType: OptionType.CALL,
3779+
maxStrikePriceMultiple: "0",
3780+
minOptionDuration: (SECS_IN_A_DAY * 0.5).toString(),
3781+
maxOptionDuration: (SECS_IN_A_DAY * 80).toString(),
3782+
maxPriceSignalAge: "0",
3783+
optionMarketAddress: calls.address,
3784+
impliedVolBips: "5000",
3785+
nftProperties: [
3786+
{
3787+
propertyValidator: "0x0000000000000000000000000000000000000000",
3788+
propertyData: [],
3789+
},
3790+
],
3791+
skewDecimal: "0",
3792+
riskFreeRateBips: "500",
3793+
};
3794+
3795+
const signedOrder = await signVolOrder(volOrder, buyer, protocol.address);
3796+
3797+
const { types, domain, value } = genVolOrderTypedData(
3798+
volOrder,
3799+
protocol.address
3800+
);
3801+
3802+
const orderHash = _TypedDataEncoder.hash(domain, types, value);
3803+
3804+
const orderValiditySignature = await admin.signMessage(
3805+
ethers.utils.arrayify(
3806+
ethers.utils.solidityKeccak256(
3807+
["bytes32", "uint256"],
3808+
[orderHash, expiration]
3809+
)
3810+
)
3811+
);
3812+
3813+
const { v, r, s } = ethers.utils.splitSignature(orderValiditySignature);
3814+
3815+
const orderValidityClaim = {
3816+
orderHash: orderHash,
3817+
goodTilTimestamp: expiration,
3818+
v,
3819+
r,
3820+
s,
3821+
};
3822+
3823+
const {
3824+
v: v2,
3825+
r: r2,
3826+
s: s2,
3827+
} = ethers.utils.splitSignature(
3828+
await admin.signMessage(
3829+
ethers.utils.arrayify(
3830+
ethers.utils.solidityKeccak256(
3831+
["uint256", "uint256", "uint256"],
3832+
["900", Math.floor(blockTimestamp - 10).toString(), expiration]
3833+
)
3834+
)
3835+
)
3836+
);
3837+
3838+
const assetPriceClaim = {
3839+
assetPriceInWei: "900",
3840+
priceObservedTimestamp: Math.floor(blockTimestamp - 10).toString(),
3841+
goodTilTimestamp: expiration,
3842+
v: v2,
3843+
r: r2,
3844+
s: s2,
3845+
};
3846+
3847+
calls.connect(writer).setApprovalForAll(bidPool.address, true);
3848+
weth.connect(buyer).deposit({ value: 1000 });
3849+
weth.connect(buyer).approve(bidPool.address, 1000);
3850+
bidPool
3851+
.connect(writer)
3852+
.sellOption(
3853+
volOrder,
3854+
signedOrder,
3855+
assetPriceClaim,
3856+
orderValidityClaim,
3857+
BigNumber.from("14"),
3858+
calls.address,
3859+
BigNumber.from(optionTokenId)
3860+
);
3861+
});
36213862
});
36223863
});

src/HookBidPool.sol

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ import "./mixin/EIP712.sol";
4949
import "./interfaces/IHookProtocol.sol";
5050
import "./interfaces/IHookOption.sol";
5151

52+
import "./interfaces/delegate-cash/IDelegationRegistry.sol";
53+
5254
/// @notice HookBidPools allows users to make off-chain orders in terms of an implied volatility which
5355
/// can later be filled by an option seller. The price of the sell will be computed using the Black-Scholes
5456
/// model at bid time.
@@ -58,7 +60,7 @@ import "./interfaces/IHookOption.sol";
5860
///
5961
/// In order for an order to be filled, it must be signed by the maker and the maker must have enough balance
6062
/// to provide the order proceeds and relevant fees. The maximum bid the order maker has offered is computed
61-
/// using the volatiliy and risk-free rate signed into the order. This information is combined with the NFT
63+
/// using the volatility and risk-free rate signed into the order. This information is combined with the NFT
6264
/// floor price provided by the off-chain oracle to compute the maximum bid price.
6365
/// If the amount of consideration requested by the seller + the protocol fees is less than the maximum bid,
6466
/// the order can then be filled. The seller will receive their requested proceeds, the protocol will receive
@@ -181,6 +183,9 @@ contract HookBidPool is EIP712, ReentrancyGuard, AccessControl {
181183
// 1% = 0.01, 100 bips = 1%, 10000 bps = 100% == 1
182184
uint256 constant BPS_TO_DECIMAL = 10e14;
183185

186+
/// https://github.com/delegatecash/delegation-registry
187+
IDelegationRegistry constant DELEGATE_CASH_REGISTRY = IDelegationRegistry(0x00000000000076A84feF008CDAbe6409d2FE638B);
188+
184189
/// ROLE CONSTANTS ///
185190

186191
/// @notice the role that can pause the contract - should be held by a mulitsig
@@ -418,7 +423,9 @@ contract HookBidPool is EIP712, ReentrancyGuard, AccessControl {
418423
require(claim.goodTilTimestamp > block.timestamp, "Claim is expired");
419424
}
420425

421-
/// @notice validates the EIP-712 signature for the order
426+
/// @notice validates the EIP-712 signature for the order. If the order maker has
427+
/// delegated rights for this contract to a different signer, then orders signed by
428+
/// that signer are also be considered valid.
422429
///
423430
/// @param hash the EIP-721 hash of the order struct
424431
/// @param maker the maker of the order, who should have signed the order
@@ -430,7 +437,13 @@ contract HookBidPool is EIP712, ReentrancyGuard, AccessControl {
430437
{
431438
address signer = ecrecover(hash, orderSignature.v, orderSignature.r, orderSignature.s);
432439
require(signer != address(0), "Order signature is invalid"); // sanity check - maker should not be 0
433-
require(signer == maker, "Order signature is invalid");
440+
if (signer == maker) {
441+
// if the order maker signed the order, than accept the signer's signature
442+
return;
443+
}
444+
// If the maker has delegated control of this contract to a different signer,
445+
// then accept this signed order as a valid signature.
446+
require(DELEGATE_CASH_REGISTRY.checkDelegateForContract(signer, maker, address(this)), "Order signature is invalid");
434447
}
435448

436449
/// @dev modifies the supplied base implied volatility to account for skew.

0 commit comments

Comments
 (0)