// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;
/**
@notice Encapsulation of the logic to produce EIP712 hashed domain and messages.
Also to produce / verify hashed and signed Orders.
See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md
See/attribute https://github.com/0xProject/0x-monorepo/blob/development/contracts/utils/contracts/src/LibEIP712.sol
*/
library Hash {
/// @dev struct represents the attributes of an offchain Swivel.Order
struct Order {
bytes32 key;
address maker;
address underlying;
bool vault;
bool exit;
uint256 principal;
uint256 premium;
uint256 maturity;
uint256 expiry;
}
// EIP712 Domain Separator typeHash
// keccak256(abi.encodePacked(
// 'EIP712Domain(',
// 'string name,',
// 'string version,',
// 'uint256 chainId,',
// 'address verifyingContract',
// ')'
// ));
bytes32 constant internal DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
// EIP712 typeHash of an Order
// keccak256(abi.encodePacked(
// 'Order(',
// 'bytes32 key,',
// 'address maker,',
// 'address underlying,',
// 'bool vault,',
// 'bool exit,',
// 'uint256 principal,',
// 'uint256 premium,',
// 'uint256 maturity,',
// 'uint256 expiry',
// ')'
// ));
bytes32 constant internal ORDER_TYPEHASH = 0x7ddd38ab5ed1c16b61ca90eeb9579e29da1ba821cf42d8cdef8f30a31a6a4146;
/// @param n EIP712 domain name
/// @param version EIP712 semantic version string
/// @param i Chain ID
/// @param verifier address of the verifying contract
function domain(string memory n, string memory version, uint256 i, address verifier) internal pure returns (bytes32) {
bytes32 hash;
assembly {
let nameHash := keccak256(add(n, 32), mload(n))
let versionHash := keccak256(add(version, 32), mload(version))
let pointer := mload(64)
mstore(pointer, DOMAIN_TYPEHASH)
mstore(add(pointer, 32), nameHash)
mstore(add(pointer, 64), versionHash)
mstore(add(pointer, 96), i)
mstore(add(pointer, 128), verifier)
hash := keccak256(pointer, 160)
}
return hash;
}
/// @param d Type hash of the domain separator (see Hash.domain)
/// @param h EIP712 hash struct (order for example)
function message(bytes32 d, bytes32 h) internal pure returns (bytes32) {
bytes32 hash;
assembly {
let pointer := mload(64)
mstore(pointer, 0x1901000000000000000000000000000000000000000000000000000000000000)
mstore(add(pointer, 2), d)
mstore(add(pointer, 34), h)
hash := keccak256(pointer, 66)
}
return hash;
}
/// @param o A Swivel Order
function order(Order calldata o) internal pure returns (bytes32) {
// TODO assembly
return keccak256(abi.encode(
ORDER_TYPEHASH,
o.key,
o.maker,
o.underlying,
o.vault,
o.exit,
o.principal,
o.premium,
o.maturity,
o.expiry
));
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;
interface Erc20 {
function approve(address, uint256) external returns (bool);
function transfer(address, uint256) external returns (bool);
function balanceOf(address) external returns (uint256);
function transferFrom(address, address, uint256) external returns (bool);
}
interface CErc20 {
function mint(uint256) external returns (uint256);
function redeemUnderlying(uint256) external returns (uint256);
}
interface MarketPlace {
// adds notional and mints zctokens
function mintZcTokenAddingNotional(address, uint256, address, uint256) external returns (bool);
// removes notional and burns zctokens
function burnZcTokenRemovingNotional(address, uint256, address, uint256) external returns (bool);
// returns the amount of underlying principal to send
function redeemZcToken(address, uint256, address, uint256) external returns (uint256);
// returns the amount of underlying interest to send
function redeemVaultInterest(address, uint256, address) external returns (uint256);
// returns the cToken address for a given market
function cTokenAddress(address, uint256) external returns (address);
// EVFZE FF EZFVE call this which would then burn zctoken and remove notional
function custodialExit(address, uint256, address, address, uint256) external returns (bool);
// IVFZI && IZFVI call this which would then mint zctoken and add notional
function custodialInitiate(address, uint256, address, address, uint256) external returns (bool);
// IZFZE && EZFZI call this, tranferring zctoken from one party to another
function p2pZcTokenExchange(address, uint256, address, address, uint256) external returns (bool);
// IVFVE && EVFVI call this, removing notional from one party and adding to the other
function p2pVaultExchange(address, uint256, address, address, uint256) external returns (bool);
// IVFZI && IVFVE call this which then transfers notional from msg.sender (taker) to swivel
function transferVaultNotionalFee(address, uint256, address, uint256) external returns (bool);
}
// SPDX-License-Identifier: UNLICENSED
// Adapted from: https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol
pragma solidity 0.8.4;
import {Erc20} from "./Interfaces.sol";
/**
@notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
@author Modified from Gnosis (https://github.com/gnosis/gp-v2-contracts/blob/main/src/contracts/libraries/GPv2SafeERC20.sol)
@dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
*/
library Safe {
/// @param e Erc20 token to execute the call with
/// @param t To address
/// @param a Amount being transferred
function approve(Erc20 e, address t, uint256 a) internal {
bool result;
assembly {
// Get a pointer to some free memory.
let pointer := mload(0x40)
// Write the abi-encoded calldata to memory piece by piece:
mstore(pointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
mstore(add(pointer, 4), and(t, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
mstore(add(pointer, 36), a) // Finally append the "amount" argument. No mask as it's a full 32 byte value.
// Call the token and store if it succeeded or not.
// We use 68 because the calldata length is 4 + 32 * 2.
result := call(gas(), e, 0, pointer, 68, 0, 0)
}
require(success(result), "approve failed");
}
/// @param e Erc20 token to execute the call with
/// @param t To address
/// @param a Amount being transferred
function transfer(Erc20 e, address t, uint256 a) internal {
bool result;
assembly {
// Get a pointer to some free memory.
let pointer := mload(0x40)
// Write the abi-encoded calldata to memory piece by piece:
mstore(pointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
mstore(add(pointer, 4), and(t, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
mstore(add(pointer, 36), a) // Finally append the "amount" argument. No mask as it's a full 32 byte value.
// Call the token and store if it succeeded or not.
// We use 68 because the calldata length is 4 + 32 * 2.
result := call(gas(), e, 0, pointer, 68, 0, 0)
}
require(success(result), "transfer failed");
}
/// @param e Erc20 token to execute the call with
/// @param f From address
/// @param t To address
/// @param a Amount being transferred
function transferFrom(Erc20 e, address f, address t, uint256 a) internal {
bool result;
assembly {
// Get a pointer to some free memory.
let pointer := mload(0x40)
// Write the abi-encoded calldata to memory piece by piece:
mstore(pointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
mstore(add(pointer, 4), and(f, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "from" argument.
mstore(add(pointer, 36), and(t, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
mstore(add(pointer, 68), a) // Finally append the "amount" argument. No mask as it's a full 32 byte value.
// Call the token and store if it succeeded or not.
// We use 100 because the calldata length is 4 + 32 * 3.
result := call(gas(), e, 0, pointer, 100, 0, 0)
}
require(success(result), "transfer from failed");
}
/// @notice normalize the acceptable values of true or null vs the unacceptable value of false (or something malformed)
/// @param r Return value from the assembly `call()` to Erc20['selector']
function success(bool r) private pure returns (bool) {
bool result;
assembly {
// Get how many bytes the call returned.
let returnDataSize := returndatasize()
// If the call reverted:
if iszero(r) {
// Copy the revert message into memory.
returndatacopy(0, 0, returnDataSize)
// Revert with the same message.
revert(0, returnDataSize)
}
switch returnDataSize
case 32 {
// Copy the return data into memory.
returndatacopy(0, 0, returnDataSize)
// Set success to whether it returned true.
result := iszero(iszero(mload(0)))
}
case 0 {
// There was no return data.
result := 1
}
default {
// It returned some malformed input.
result := 0
}
}
return result;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;
library Sig {
/// @dev ECDSA V,R and S components encapsulated here as we may not always be able to accept a bytes signature
struct Components {
uint8 v;
bytes32 r;
bytes32 s;
}
/// @param h Hashed data which was originally signed
/// @param c signature struct containing V,R and S
/// @return The recovered address
function recover(bytes32 h, Components calldata c) internal pure returns (address) {
// EIP-2 and malleable signatures...
// see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol
require(uint256(c.s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, 'invalid signature "s" value');
require(c.v == 27 || c.v == 28, 'invalid signature "v" value');
return ecrecover(h, c.v, c.r, c.s);
}
/// @param h Hashed data which was originally signed
/// @param sig Valid ECDSA signature
/// @dev splitAndRecover should only be used if it is known that the resulting
/// verifying bit (V) will be 27 || 28. Otherwise use recover, possibly calling split first.
/// @return The recovered address
function splitAndRecover(bytes32 h, bytes memory sig) internal pure returns (address) {
(uint8 v, bytes32 r, bytes32 s) = split(sig);
return ecrecover(h, v, r, s);
}
/// @param sig Valid ECDSA signature
/// @return v The verification bit
/// @return r First 32 bytes
/// @return s Next 32 bytes
function split(bytes memory sig) internal pure returns (uint8, bytes32, bytes32) {
require(sig.length == 65, 'invalid signature length');
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;
import './Interfaces.sol';
import './Hash.sol';
import './Sig.sol';
import './Safe.sol';
contract Swivel {
/// @dev maps the key of an order to a boolean indicating if an order was cancelled
mapping (bytes32 => bool) public cancelled;
/// @dev maps the key of an order to an amount representing its taken volume
mapping (bytes32 => uint256) public filled;
/// @dev maps a token address to a point in time, a hold, after which a withdrawal can be made
mapping (address => uint256) public withdrawals;
string constant public NAME = 'Swivel Finance';
string constant public VERSION = '2.0.0';
uint256 constant public HOLD = 3 days;
bytes32 public immutable domain;
address public immutable marketPlace;
address public admin;
uint16 constant public MIN_FEENOMINATOR = 33;
/// @dev holds the fee demoninators for [zcTokenInitiate, zcTokenExit, vaultInitiate, vaultExit]
uint16[4] public feenominators;
/// @notice Emitted on order cancellation
event Cancel (bytes32 indexed key, bytes32 hash);
/// @notice Emitted on any initiate*
/// @dev filled is 'principalFilled' when (vault:false, exit:false) && (vault:true, exit:true)
/// @dev filled is 'premiumFilled' when (vault:true, exit:false) && (vault:false, exit:true)
event Initiate(bytes32 indexed key, bytes32 hash, address indexed maker, bool vault, bool exit, address indexed sender, uint256 amount, uint256 filled);
/// @notice Emitted on any exit*
/// @dev filled is 'principalFilled' when (vault:false, exit:false) && (vault:true, exit:true)
/// @dev filled is 'premiumFilled' when (vault:true, exit:false) && (vault:false, exit:true)
event Exit(bytes32 indexed key, bytes32 hash, address indexed maker, bool vault, bool exit, address indexed sender, uint256 amount, uint256 filled);
/// @notice Emitted on token withdrawal scheduling
event ScheduleWithdrawal(address indexed token, uint256 hold);
/// @notice Emitted on token withdrawal blocking
event BlockWithdrawal(address indexed token);
/// @notice Emitted on a change to the feenominators array
event SetFee(uint256 indexed index, uint256 indexed feenominator);
/// @param m deployed MarketPlace contract address
constructor(address m) {
admin = msg.sender;
domain = Hash.domain(NAME, VERSION, block.chainid, address(this));
marketPlace = m;
feenominators = [200, 600, 400, 200];
}
// ********* INITIATING *************
/// @notice Allows a user to initiate a position
/// @param o Array of offline Swivel.Orders
/// @param a Array of order volume (principal) amounts relative to passed orders
/// @param c Array of Components from valid ECDSA signatures
function initiate(Hash.Order[] calldata o, uint256[] calldata a, Sig.Components[] calldata c) external returns (bool) {
uint256 len = o.length;
// for each order filled, routes the order to the right interaction depending on its params
for (uint256 i; i < len; i++) {
Hash.Order memory order = o[i];
if (!order.exit) {
if (!order.vault) {
initiateVaultFillingZcTokenInitiate(o[i], a[i], c[i]);
} else {
initiateZcTokenFillingVaultInitiate(o[i], a[i], c[i]);
}
} else {
if (!order.vault) {
initiateZcTokenFillingZcTokenExit(o[i], a[i], c[i]);
} else {
initiateVaultFillingVaultExit(o[i], a[i], c[i]);
}
}
}
return true;
}
/// @notice Allows a user to initiate a Vault by filling an offline zcToken initiate order
/// @dev This method should pass (underlying, maturity, maker, sender, principalFilled) to MarketPlace.custodialInitiate
/// @param o Order being filled
/// @param a Amount of volume (premium) being filled by the taker's initiate
/// @param c Components of a valid ECDSA signature
function initiateVaultFillingZcTokenInitiate(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal {
// checks order signature, order cancellation and order expiry
bytes32 hash = validOrderHash(o, c);
// checks the side, and the amount compared to available
require((a + filled[hash]) <= o.premium, 'taker amount > available volume');
filled[hash] += a;
// transfer underlying tokens
Erc20 uToken = Erc20(o.underlying);
Safe.transferFrom(uToken, msg.sender, o.maker, a);
uint256 principalFilled = (a * o.principal) / o.premium;
Safe.transferFrom(uToken, o.maker, address(this), principalFilled);
MarketPlace mPlace = MarketPlace(marketPlace);
// mint tokens
require(CErc20(mPlace.cTokenAddress(o.underlying, o.maturity)).mint(principalFilled) == 0, 'minting CToken failed');
// alert marketplace
require(mPlace.custodialInitiate(o.underlying, o.maturity, o.maker, msg.sender, principalFilled), 'custodial initiate failed');
// transfer fee in vault notional to swivel (from msg.sender)
uint256 fee = principalFilled / feenominators[2];
require(mPlace.transferVaultNotionalFee(o.underlying, o.maturity, msg.sender, fee), 'notional fee transfer failed');
emit Initiate(o.key, hash, o.maker, o.vault, o.exit, msg.sender, a, principalFilled);
}
/// @notice Allows a user to initiate a zcToken by filling an offline vault initiate order
/// @dev This method should pass (underlying, maturity, sender, maker, a) to MarketPlace.custodialInitiate
/// @param o Order being filled
/// @param a Amount of volume (principal) being filled by the taker's initiate
/// @param c Components of a valid ECDSA signature
function initiateZcTokenFillingVaultInitiate(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal {
bytes32 hash = validOrderHash(o, c);
require((a + filled[hash]) <= o.principal, 'taker amount > available volume');
filled[hash] += a;
Erc20 uToken = Erc20(o.underlying);
uint256 premiumFilled = (a * o.premium) / o.principal;
Safe.transferFrom(uToken, o.maker, msg.sender, premiumFilled);
// transfer principal + fee in underlying to swivel (from sender)
uint256 fee = premiumFilled / feenominators[0];
Safe.transferFrom(uToken, msg.sender, address(this), (a + fee));
MarketPlace mPlace = MarketPlace(marketPlace);
// mint tokens
require(CErc20(mPlace.cTokenAddress(o.underlying, o.maturity)).mint(a) == 0, 'minting CToken Failed');
// alert marketplace
require(mPlace.custodialInitiate(o.underlying, o.maturity, msg.sender, o.maker, a), 'custodial initiate failed');
emit Initiate(o.key, hash, o.maker, o.vault, o.exit, msg.sender, a, premiumFilled);
}
/// @notice Allows a user to initiate zcToken? by filling an offline zcToken exit order
/// @dev This method should pass (underlying, maturity, maker, sender, a) to MarketPlace.p2pZcTokenExchange
/// @param o Order being filled
/// @param a Amount of volume (principal) being filled by the taker's initiate
/// @param c Components of a valid ECDSA signature
function initiateZcTokenFillingZcTokenExit(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal {
bytes32 hash = validOrderHash(o, c);
require((a + filled[hash]) <= o.principal, 'taker amount > available volume');
filled[hash] += a;
uint256 premiumFilled = (a * o.premium) / o.principal;
Erc20 uToken = Erc20(o.underlying);
// transfer underlying tokens, then take fee
Safe.transferFrom(uToken, msg.sender, o.maker, a - premiumFilled);
uint256 fee = premiumFilled / feenominators[0];
Safe.transferFrom(uToken, msg.sender, address(this), fee);
// alert marketplace
require(MarketPlace(marketPlace).p2pZcTokenExchange(o.underlying, o.maturity, o.maker, msg.sender, a), 'zcToken exchange failed');
emit Initiate(o.key, hash, o.maker, o.vault, o.exit, msg.sender, a, premiumFilled);
}
/// @notice Allows a user to initiate a Vault by filling an offline vault exit order
/// @dev This method should pass (underlying, maturity, maker, sender, principalFilled) to MarketPlace.p2pVaultExchange
/// @param o Order being filled
/// @param a Amount of volume (interest) being filled by the taker's exit
/// @param c Components of a valid ECDSA signature
function initiateVaultFillingVaultExit(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal {
bytes32 hash = validOrderHash(o, c);
require((a + filled[hash]) <= o.premium, 'taker amount > available volume');
filled[hash] += a;
Safe.transferFrom(Erc20(o.underlying), msg.sender, o.maker, a);
MarketPlace mPlace = MarketPlace(marketPlace);
uint256 principalFilled = (a * o.principal) / o.premium;
// alert marketplace
require(mPlace.p2pVaultExchange(o.underlying, o.maturity, o.maker, msg.sender, principalFilled), 'vault exchange failed');
// transfer fee (in vault notional) to swivel
uint256 fee = principalFilled / feenominators[2];
require(mPlace.transferVaultNotionalFee(o.underlying, o.maturity, msg.sender, fee), "notional fee transfer failed");
emit Initiate(o.key, hash, o.maker, o.vault, o.exit, msg.sender, a, principalFilled);
}
// ********* EXITING ***************
/// @notice Allows a user to exit (sell) a currently held position to the marketplace.
/// @param o Array of offline Swivel.Orders
/// @param a Array of order volume (principal) amounts relative to passed orders
/// @param c Components of a valid ECDSA signature
function exit(Hash.Order[] calldata o, uint256[] calldata a, Sig.Components[] calldata c) external returns (bool) {
uint256 len = o.length;
// for each order filled, routes the order to the right interaction depending on its params
for (uint256 i; i < len; i++) {
Hash.Order memory order = o[i];
// if the order being filled is not an exit
if (!order.exit) {
// if the order being filled is a vault initiate or a zcToken initiate
if (!order.vault) {
// if filling a zcToken initiate with an exit, one is exiting zcTokens
exitZcTokenFillingZcTokenInitiate(o[i], a[i], c[i]);
} else {
// if filling a vault initiate with an exit, one is exiting vault notional
exitVaultFillingVaultInitiate(o[i], a[i], c[i]);
}
} else {
// if the order being filled is a vault exit or a zcToken exit
if (!order.vault) {
// if filling a zcToken exit with an exit, one is exiting vault
exitVaultFillingZcTokenExit(o[i], a[i], c[i]);
} else {
// if filling a vault exit with an exit, one is exiting zcTokens
exitZcTokenFillingVaultExit(o[i], a[i], c[i]);
}
}
}
return true;
}
/// @notice Allows a user to exit their zcTokens by filling an offline zcToken initiate order
/// @dev This method should pass (underlying, maturity, sender, maker, principalFilled) to MarketPlace.p2pZcTokenExchange
/// @param o Order being filled
/// @param a Amount of volume (interest) being filled by the taker's exit
/// @param c Components of a valid ECDSA signature
function exitZcTokenFillingZcTokenInitiate(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal {
bytes32 hash = validOrderHash(o, c);
require((a + filled[hash]) <= o.premium, 'taker amount > available volume');
filled[hash] += a;
Erc20 uToken = Erc20(o.underlying);
uint256 principalFilled = (a * o.principal) / o.premium;
// transfer underlying from initiating party to exiting party, minus the price the exit party pays for the exit (premium), and the fee.
Safe.transferFrom(uToken, o.maker, msg.sender, principalFilled - a);
// transfer fee in underlying to swivel
uint256 fee = principalFilled / feenominators[1];
Safe.transferFrom(uToken, o.maker, address(this), fee);
// alert marketplace
require(MarketPlace(marketPlace).p2pZcTokenExchange(o.underlying, o.maturity, msg.sender, o.maker, principalFilled), 'zcToken exchange failed');
emit Exit(o.key, hash, o.maker, o.vault, o.exit, msg.sender, a, principalFilled);
}
/// @notice Allows a user to exit their Vault by filling an offline vault initiate order
/// @dev This method should pass (underlying, maturity, sender, maker, a) to MarketPlace.p2pVaultExchange
/// @param o Order being filled
/// @param a Amount of volume (principal) being filled by the taker's exit
/// @param c Components of a valid ECDSA signature
function exitVaultFillingVaultInitiate(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal {
bytes32 hash = validOrderHash(o, c);
require((a + filled[hash]) <= o.principal, 'taker amount > available volume');
filled[hash] += a;
Erc20 uToken = Erc20(o.underlying);
// transfer premium from maker to sender
uint256 premiumFilled = (a * o.premium) / o.principal;
Safe.transferFrom(uToken, o.maker, msg.sender, premiumFilled);
uint256 fee = premiumFilled / feenominators[3];
// transfer fee in underlying to swivel from sender
Safe.transferFrom(uToken, msg.sender, address(this), fee);
// transfer <a> notional from sender to maker
require(MarketPlace(marketPlace).p2pVaultExchange(o.underlying, o.maturity, msg.sender, o.maker, a), 'vault exchange failed');
emit Exit(o.key, hash, o.maker, o.vault, o.exit, msg.sender, a, premiumFilled);
}
/// @notice Allows a user to exit their Vault filling an offline zcToken exit order
/// @dev This method should pass (underlying, maturity, maker, sender, a) to MarketPlace.exitFillingExit
/// @param o Order being filled
/// @param a Amount of volume (principal) being filled by the taker's exit
/// @param c Components of a valid ECDSA signature
function exitVaultFillingZcTokenExit(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal {
bytes32 hash = validOrderHash(o, c);
require((a + filled[hash]) <= o.principal, 'taker amount > available volume');
filled[hash] += a;
// redeem underlying on Compound and burn cTokens
MarketPlace mPlace = MarketPlace(marketPlace);
address cTokenAddr = mPlace.cTokenAddress(o.underlying, o.maturity);
require((CErc20(cTokenAddr).redeemUnderlying(a) == 0), "compound redemption error");
Erc20 uToken = Erc20(o.underlying);
// transfer principal-premium back to fixed exit party now that the interest coupon and zcb have been redeemed
uint256 premiumFilled = (a * o.premium) / o.principal;
Safe.transfer(uToken, o.maker, a - premiumFilled);
// transfer premium-fee to floating exit party
uint256 fee = premiumFilled / feenominators[3];
Safe.transfer(uToken, msg.sender, premiumFilled - fee);
// burn zcTokens + nTokens from o.maker and msg.sender respectively
require(mPlace.custodialExit(o.underlying, o.maturity, o.maker, msg.sender, a), 'custodial exit failed');
emit Exit(o.key, hash, o.maker, o.vault, o.exit, msg.sender, a, premiumFilled);
}
/// @notice Allows a user to exit their zcTokens by filling an offline vault exit order
/// @dev This method should pass (underlying, maturity, sender, maker, principalFilled) to MarketPlace.exitFillingExit
/// @param o Order being filled
/// @param a Amount of volume (interest) being filled by the taker's exit
/// @param c Components of a valid ECDSA signature
function exitZcTokenFillingVaultExit(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal {
bytes32 hash = validOrderHash(o, c);
require((a + filled[hash]) <= o.premium, 'taker amount > available volume');
filled[hash] += a;
// redeem underlying on Compound and burn cTokens
MarketPlace mPlace = MarketPlace(marketPlace);
address cTokenAddr = mPlace.cTokenAddress(o.underlying, o.maturity);
uint256 principalFilled = (a * o.principal) / o.premium;
require((CErc20(cTokenAddr).redeemUnderlying(principalFilled) == 0), "compound redemption error");
Erc20 uToken = Erc20(o.underlying);
// transfer principal-premium-fee back to fixed exit party now that the interest coupon and zcb have been redeemed
uint256 fee = principalFilled / feenominators[1];
Safe.transfer(uToken, msg.sender, principalFilled - a - fee);
Safe.transfer(uToken, o.maker, a);
// burn <principalFilled> zcTokens + nTokens from msg.sender and o.maker respectively
require(mPlace.custodialExit(o.underlying, o.maturity, msg.sender, o.maker, principalFilled), 'custodial exit failed');
emit Exit(o.key, hash, o.maker, o.vault, o.exit, msg.sender, a, principalFilled);
}
/// @notice Allows a user to cancel an order, preventing it from being filled in the future
/// @param o Order being cancelled
/// @param c Components of a valid ECDSA signature
function cancel(Hash.Order calldata o, Sig.Components calldata c) external returns (bool) {
bytes32 hash = validOrderHash(o, c);
require(msg.sender == o.maker, 'sender must be maker');
cancelled[hash] = true;
emit Cancel(o.key, hash);
return true;
}
// ********* ADMINISTRATIVE ***************
/// @param a Address of a new admin
function transferAdmin(address a) external authorized(admin) returns (bool) {
admin = a;
return true;
}
/// @notice Allows the admin to schedule the withdrawal of tokens
/// @param e Address of (erc20) token to withdraw
function scheduleWithdrawal(address e) external authorized(admin) returns (bool) {
uint256 when = block.timestamp + HOLD;
withdrawals[e] = when;
emit ScheduleWithdrawal(e, when);
return true;
}
/// @notice Emergency function to block unplanned withdrawals
/// @param e Address of token withdrawal to block
function blockWithdrawal(address e) external authorized(admin) returns (bool) {
withdrawals[e] = 0;
emit BlockWithdrawal(e);
return true;
}
/// @notice Allows the admin to withdraw the given token, provided the holding period has been observed
/// @param e Address of token to withdraw
function withdraw(address e) external authorized(admin) returns (bool) {
uint256 when = withdrawals[e];
require (when != 0, 'no withdrawal scheduled');
require (block.timestamp >= when, 'withdrawal still on hold');
withdrawals[e] = 0;
Erc20 token = Erc20(e);
Safe.transfer(token, admin, token.balanceOf(address(this)));
return true;
}
/// @notice Allows the admin to set a new fee denominator
/// @param i The index of the new fee denominator
/// @param d The new fee denominator
function setFee(uint16 i, uint16 d) external authorized(admin) returns (bool) {
require(d >= MIN_FEENOMINATOR, 'fee too high');
feenominators[i] = d;
emit SetFee(i, d);
return true;
}
/// @notice Allows the admin to bulk approve given compound addresses at the underlying token, saving marginal approvals
/// @param u array of underlying token addresses
/// @param c array of compound token addresses
function approveUnderlying(address[] calldata u, address[] calldata c) external authorized(admin) returns (bool) {
uint256 len = u.length;
require (len == c.length, 'array length mismatch');
uint256 max = 2**256 - 1;
for (uint256 i; i < len; i++) {
Erc20 uToken = Erc20(u[i]);
Safe.approve(uToken, c[i], max);
}
return true;
}
// ********* PROTOCOL UTILITY ***************
/// @notice Allows users to deposit underlying and in the process split it into/mint
/// zcTokens and vault notional. Calls mPlace.mintZcTokenAddingNotional
/// @param u Underlying token address associated with the market
/// @param m Maturity timestamp of the market
/// @param a Amount of underlying being deposited
function splitUnderlying(address u, uint256 m, uint256 a) external returns (bool) {
Erc20 uToken = Erc20(u);
Safe.transferFrom(uToken, msg.sender, address(this), a);
MarketPlace mPlace = MarketPlace(marketPlace);
require(CErc20(mPlace.cTokenAddress(u, m)).mint(a) == 0, 'minting CToken Failed');
require(mPlace.mintZcTokenAddingNotional(u, m, msg.sender, a), 'mint ZcToken adding Notional failed');
return true;
}
/// @notice Allows users deposit/burn 1-1 amounts of both zcTokens and vault notional,
/// in the process "combining" the two, and redeeming underlying. Calls mPlace.burnZcTokenRemovingNotional.
/// @param u Underlying token address associated with the market
/// @param m Maturity timestamp of the market
/// @param a Amount of zcTokens being redeemed
function combineTokens(address u, uint256 m, uint256 a) external returns (bool) {
MarketPlace mPlace = MarketPlace(marketPlace);
require(mPlace.burnZcTokenRemovingNotional(u, m, msg.sender, a), 'burn ZcToken removing Notional failed');
address cTokenAddr = mPlace.cTokenAddress(u, m);
require((CErc20(cTokenAddr).redeemUnderlying(a) == 0), "compound redemption error");
Safe.transfer(Erc20(u), msg.sender, a);
return true;
}
/// @notice Allows zcToken holders to redeem their tokens for underlying tokens after maturity has been reached (via MarketPlace).
/// @param u Underlying token address associated with the market
/// @param m Maturity timestamp of the market
/// @param a Amount of zcTokens being redeemed
function redeemZcToken(address u, uint256 m, uint256 a) external returns (bool) {
MarketPlace mPlace = MarketPlace(marketPlace);
// call marketplace to determine the amount redeemed
uint256 redeemed = mPlace.redeemZcToken(u, m, msg.sender, a);
// redeem underlying from compound
require(CErc20(mPlace.cTokenAddress(u, m)).redeemUnderlying(redeemed) == 0, 'compound redemption failed');
// transfer underlying back to msg.sender
Safe.transfer(Erc20(u), msg.sender, redeemed);
return true;
}
/// @notice Allows Vault owners to redeem any currently accrued interest (via MarketPlace)
/// @param u Underlying token address associated with the market
/// @param m Maturity timestamp of the market
function redeemVaultInterest(address u, uint256 m) external returns (bool) {
MarketPlace mPlace = MarketPlace(marketPlace);
// call marketplace to determine the amount redeemed
uint256 redeemed = mPlace.redeemVaultInterest(u, m, msg.sender);
// redeem underlying from compound
require(CErc20(mPlace.cTokenAddress(u, m)).redeemUnderlying(redeemed) == 0, 'compound redemption failed');
// transfer underlying back to msg.sender
Safe.transfer(Erc20(u), msg.sender, redeemed);
return true;
}
/// @notice Varifies the validity of an order and it's signature.
/// @param o An offline Swivel.Order
/// @param c Components of a valid ECDSA signature
/// @return the hashed order.
function validOrderHash(Hash.Order calldata o, Sig.Components calldata c) internal view returns (bytes32) {
bytes32 hash = Hash.order(o);
require(!cancelled[hash], 'order cancelled');
require(o.expiry >= block.timestamp, 'order expired');
require(o.maker == Sig.recover(Hash.message(domain, hash), c), 'invalid signature');
return hash;
}
modifier authorized(address a) {
require(msg.sender == a, 'sender must be authorized');
_;
}
}
{
"compilationTarget": {
"Swivel.sol": "Swivel"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 15000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"m","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"}],"name":"BlockWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"key","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"Cancel","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"key","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"maker","type":"address"},{"indexed":false,"internalType":"bool","name":"vault","type":"bool"},{"indexed":false,"internalType":"bool","name":"exit","type":"bool"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"filled","type":"uint256"}],"name":"Exit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"key","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"maker","type":"address"},{"indexed":false,"internalType":"bool","name":"vault","type":"bool"},{"indexed":false,"internalType":"bool","name":"exit","type":"bool"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"filled","type":"uint256"}],"name":"Initiate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"hold","type":"uint256"}],"name":"ScheduleWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"feenominator","type":"uint256"}],"name":"SetFee","type":"event"},{"inputs":[],"name":"HOLD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_FEENOMINATOR","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"u","type":"address[]"},{"internalType":"address[]","name":"c","type":"address[]"}],"name":"approveUnderlying","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"e","type":"address"}],"name":"blockWithdrawal","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"bool","name":"vault","type":"bool"},{"internalType":"bool","name":"exit","type":"bool"},{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint256","name":"premium","type":"uint256"},{"internalType":"uint256","name":"maturity","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"}],"internalType":"struct Hash.Order","name":"o","type":"tuple"},{"components":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct Sig.Components","name":"c","type":"tuple"}],"name":"cancel","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"cancelled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"combineTokens","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"domain","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"bool","name":"vault","type":"bool"},{"internalType":"bool","name":"exit","type":"bool"},{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint256","name":"premium","type":"uint256"},{"internalType":"uint256","name":"maturity","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"}],"internalType":"struct Hash.Order[]","name":"o","type":"tuple[]"},{"internalType":"uint256[]","name":"a","type":"uint256[]"},{"components":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct Sig.Components[]","name":"c","type":"tuple[]"}],"name":"exit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"feenominators","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"filled","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"bool","name":"vault","type":"bool"},{"internalType":"bool","name":"exit","type":"bool"},{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint256","name":"premium","type":"uint256"},{"internalType":"uint256","name":"maturity","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"}],"internalType":"struct Hash.Order[]","name":"o","type":"tuple[]"},{"internalType":"uint256[]","name":"a","type":"uint256[]"},{"components":[{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct Sig.Components[]","name":"c","type":"tuple[]"}],"name":"initiate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"marketPlace","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"}],"name":"redeemVaultInterest","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"redeemZcToken","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"e","type":"address"}],"name":"scheduleWithdrawal","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"i","type":"uint16"},{"internalType":"uint16","name":"d","type":"uint16"}],"name":"setFee","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"splitUnderlying","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"transferAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"e","type":"address"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"withdrawals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]