Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add authorization with signature #114

Merged
merged 34 commits into from
Jul 31, 2023
Merged
Changes from 4 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7a6b455
feat: add approval with signature
MerlinEgalite Jul 11, 2023
7efa793
feat: add public visibility
MerlinEgalite Jul 11, 2023
4063fc9
feat: small details
MerlinEgalite Jul 11, 2023
830c0db
feat: remove version
MerlinEgalite Jul 12, 2023
9aece4d
Merge branch 'feat/approval' of github.com:morpho-labs/blue into feat…
MerlinEgalite Jul 14, 2023
e36afcc
Merge branches 'feat/approval-with-sig' and 'main' of github.com:morp…
Rubilmax Jul 21, 2023
c1aab0a
feat: switch to lib errors
MerlinEgalite Jul 21, 2023
baad8a0
refactor: implement part of suggestions
MerlinEgalite Jul 27, 2023
f323f2c
test: add first test
MerlinEgalite Jul 27, 2023
86ccb5e
docs: apply suggestion
MerlinEgalite Jul 27, 2023
e76e5ab
Merge branch 'main' of github.com:morpho-labs/blue into feat/approval…
MerlinEgalite Jul 27, 2023
79eae36
refactor: remove _setApproval for now
MerlinEgalite Jul 27, 2023
88806e2
refactor: move deadline require up
MerlinEgalite Jul 28, 2023
30dd70f
test: update tests
MerlinEgalite Jul 28, 2023
15dbe1f
Merge branch 'main' of github.com:morpho-labs/blue into feat/approval…
MerlinEgalite Jul 28, 2023
96a8f28
style: approve with sig
MathisGD Jul 28, 2023
0872299
perf: remove nonce from args
MathisGD Jul 28, 2023
0961afa
style: signature error wording
MathisGD Jul 28, 2023
7e58d62
style: immutable case
MathisGD Jul 28, 2023
391ab28
style: harmonize authorizations naming
MathisGD Jul 28, 2023
fe70678
perf: remove redondant checks on sig
MathisGD Jul 28, 2023
6ce91a5
style: fixes after review
MathisGD Jul 28, 2023
40a3855
fix: readd name in domain
MathisGD Jul 28, 2023
7ed0975
fix: check that signatory is not address zero
MathisGD Jul 28, 2023
af2d66f
style: errors wording and tests
MathisGD Jul 31, 2023
541c883
style: sender is authorized by default
MathisGD Jul 31, 2023
f45f4d5
style: isSenderApproved function naming
MathisGD Jul 31, 2023
d5c8b13
Merge pull request #192 from morpho-labs/feat/approval-with-sig-1
MerlinEgalite Jul 31, 2023
d0b9653
Merge branch 'main' into feat/approval-with-sig
MathisGD Jul 31, 2023
200c50f
style: replace flashloans above authorizations
MathisGD Jul 31, 2023
2a68fdd
docs: simplify authorization doc
MathisGD Jul 31, 2023
a901b95
docs: add a comment on signature malleability
MathisGD Jul 31, 2023
7bf9f15
style: naming authorizee => authorized
MathisGD Jul 31, 2023
588a852
Merge branch 'main' into feat/approval-with-sig
MathisGD Jul 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 67 additions & 5 deletions src/Blue.sol
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol";
uint256 constant WAD = 1e18;
uint256 constant ALPHA = 0.5e18;

/// @dev The prefix used for EIP-712 signature.
string constant EIP712_MSG_PREFIX = "\x19\x01";
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

/// @dev The name used for EIP-712 signature.
string constant EIP712_NAME = "Blue";

/// @dev The domain typehash used for the EIP-712 signature.
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
bytes32 constant EIP712_DOMAIN_TYPEHASH =
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

/// @dev The typehash for approveManagerWithSig Authorization used for the EIP-712 signature.
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
bytes32 constant EIP712_AUTHORIZATION_TYPEHASH =
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
keccak256("Authorization(address delegator,address manager,bool isAllowed,uint256 nonce,uint256 deadline)");
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

/// @dev The highest valid value for s in an ECDSA signature pair (0 < s < secp256k1n ÷ 2 + 1).
uint256 constant MAX_VALID_ECDSA_S = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0;
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

// Market id.
type Id is bytes32;

Expand All @@ -24,6 +41,13 @@ struct Market {
uint256 lltv;
}

/// @notice Contains the `v`, `r` and `s` parameters of an ECDSA signature.
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}

using {toId} for Market;

function toId(Market calldata market) pure returns (Id) {
Expand All @@ -34,6 +58,10 @@ contract Blue {
using MathLib for uint256;
using SafeTransferLib for IERC20;

// Immutables.

bytes32 public immutable domainSeparator;
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

// Storage.

// Owner.
Expand All @@ -60,11 +88,16 @@ contract Blue {
mapping(uint256 => bool) public isLltvEnabled;
// User's managers.
mapping(address => mapping(address => bool)) public approval;
// User's nonces. Used to prevent replay attacks with EIP-712 signatures.
mapping(address => uint256) public userNonce;

// Constructor.

constructor(address newOwner) {
owner = newOwner;

domainSeparator =
keccak256(abi.encode(EIP712_DOMAIN_TYPEHASH, keccak256(bytes(EIP712_NAME)), block.chainid, address(this)));
}

// Modifiers.
Expand Down Expand Up @@ -127,7 +160,7 @@ contract Blue {
Id id = market.toId();
require(lastUpdate[id] != 0, "unknown market");
require(amount != 0, "zero amount");
require(isSenderApprovedFor(onBehalf), "not approved");
require(_isSenderApprovedFor(onBehalf), "not approved");

accrueInterests(market, id);

Expand All @@ -148,7 +181,7 @@ contract Blue {
Id id = market.toId();
require(lastUpdate[id] != 0, "unknown market");
require(amount != 0, "zero amount");
require(isSenderApprovedFor(onBehalf), "not approved");
require(_isSenderApprovedFor(onBehalf), "not approved");

accrueInterests(market, id);

Expand Down Expand Up @@ -204,7 +237,7 @@ contract Blue {
Id id = market.toId();
require(lastUpdate[id] != 0, "unknown market");
require(amount != 0, "zero amount");
require(isSenderApprovedFor(onBehalf), "not approved");
require(_isSenderApprovedFor(onBehalf), "not approved");

accrueInterests(market, id);

Expand Down Expand Up @@ -251,11 +284,40 @@ contract Blue {

// Position management.
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

function setApproval(
Rubilmax marked this conversation as resolved.
Show resolved Hide resolved
address delegator,
address manager,
bool isAllowed,
uint256 nonce,
uint256 deadline,
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
Signature calldata signature
) external {
require(uint256(signature.s) <= MAX_VALID_ECDSA_S, "invalid s");
// v ∈ {27, 28} (source: https://ethereum.github.io/yellowpaper/paper.pdf #308)
require(signature.v == 27 || signature.v == 28, "invalid v");

bytes32 structHash =
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
keccak256(abi.encode(EIP712_AUTHORIZATION_TYPEHASH, delegator, manager, isAllowed, nonce, deadline));
bytes32 digest = keccak256(abi.encodePacked(EIP712_MSG_PREFIX, domainSeparator, structHash));
address signatory = ecrecover(digest, signature.v, signature.r, signature.s);

require(signatory != address(0) && delegator == signatory, "invalid signatory");
require(block.timestamp < deadline, "signature expired");

require(nonce == userNonce[signatory]++, "invalid nonce");

_setApproval(signatory, manager, isAllowed);
}

function setApproval(address manager, bool isAllowed) external {
approval[msg.sender][manager] = isAllowed;
_setApproval(msg.sender, manager, isAllowed);
}

function _setApproval(address delegator, address manager, bool isAllowed) internal {
approval[delegator][manager] = isAllowed;
}

function isSenderApprovedFor(address user) internal view returns (bool) {
function _isSenderApprovedFor(address user) internal view returns (bool) {
return msg.sender == user || approval[user][msg.sender];
}

Expand Down