Skip to content

Commit 481470f

Browse files
committed
Add ERC3009
1 parent 8ff78ff commit 481470f

File tree

2 files changed

+290
-0
lines changed

2 files changed

+290
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity >=0.4.16;
4+
5+
interface IERC3009 {
6+
event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
7+
8+
/**
9+
* @notice Returns the state of an authorization
10+
* @dev Nonces are randomly generated 32-byte data unique to the authorizer's
11+
* address
12+
* @param authorizer Authorizer's address
13+
* @param nonce Nonce of the authorization
14+
* @return True if the nonce is used
15+
*/
16+
function authorizationState(address authorizer, bytes32 nonce) external view returns (bool);
17+
18+
/**
19+
* @notice Execute a transfer with a signed authorization
20+
* @param from Payer's address (Authorizer)
21+
* @param to Payee's address
22+
* @param value Amount to be transferred
23+
* @param validAfter The time after which this is valid (unix time)
24+
* @param validBefore The time before which this is valid (unix time)
25+
* @param nonce Unique nonce
26+
* @param v v of the signature
27+
* @param r r of the signature
28+
* @param s s of the signature
29+
*/
30+
function transferWithAuthorization(
31+
address from,
32+
address to,
33+
uint256 value,
34+
uint256 validAfter,
35+
uint256 validBefore,
36+
bytes32 nonce,
37+
uint8 v,
38+
bytes32 r,
39+
bytes32 s
40+
) external;
41+
42+
/**
43+
* @notice Receive a transfer with a signed authorization from the payer
44+
* @dev This has an additional check to ensure that the payee's address matches
45+
* the caller of this function to prevent front-running attacks. (See security
46+
* considerations)
47+
* @param from Payer's address (Authorizer)
48+
* @param to Payee's address
49+
* @param value Amount to be transferred
50+
* @param validAfter The time after which this is valid (unix time)
51+
* @param validBefore The time before which this is valid (unix time)
52+
* @param nonce Unique nonce
53+
* @param v v of the signature
54+
* @param r r of the signature
55+
* @param s s of the signature
56+
*/
57+
function receiveWithAuthorization(
58+
address from,
59+
address to,
60+
uint256 value,
61+
uint256 validAfter,
62+
uint256 validBefore,
63+
bytes32 nonce,
64+
uint8 v,
65+
bytes32 r,
66+
bytes32 s
67+
) external;
68+
}
69+
70+
interface IERC3009Cancel {
71+
event AuthorizationCanceled(address indexed authorizer, bytes32 indexed nonce);
72+
73+
/**
74+
* @notice Attempt to cancel an authorization
75+
* @param authorizer Authorizer's address
76+
* @param nonce Nonce of the authorization
77+
* @param v v of the signature
78+
* @param r r of the signature
79+
* @param s s of the signature
80+
*/
81+
function cancelAuthorization(address authorizer, bytes32 nonce, uint8 v, bytes32 r, bytes32 s) external;
82+
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {ERC20} from "../ERC20.sol";
5+
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
6+
import {SignatureChecker} from "../../../utils/cryptography/SignatureChecker.sol";
7+
import {MessageHashUtils} from "../../../utils/cryptography/MessageHashUtils.sol";
8+
import {IERC3009, IERC3009Cancel} from "../../../interfaces/draft-IERC3009.sol";
9+
10+
abstract contract ERC20TransferAuthorization is ERC20, EIP712, IERC3009, IERC3009Cancel {
11+
/// @dev The signature is invalid
12+
error ERC3009InvalidSignature();
13+
14+
/// @dev The authorization is already used or canceled
15+
error ERC3009ConsumedAuthorization(address authorizer, bytes32 nonce);
16+
17+
/// @dev The authorization is not valid at the given time
18+
error ERC3009InvalidAuthorizationTime(uint256 validAfter, uint256 validBefore);
19+
20+
bytes32 private constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH =
21+
keccak256(
22+
"TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
23+
);
24+
bytes32 private constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH =
25+
keccak256(
26+
"ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
27+
);
28+
bytes32 private constant CANCEL_AUTHORIZATION_TYPEHASH =
29+
keccak256("CancelAuthorization(address authorizer,bytes32 nonce)");
30+
31+
mapping(address => mapping(bytes32 => bool)) private _consumed;
32+
33+
/// @inheritdoc IERC3009
34+
function authorizationState(address authorizer, bytes32 nonce) public view virtual returns (bool) {
35+
return _consumed[authorizer][nonce];
36+
}
37+
38+
/// @inheritdoc IERC3009
39+
function transferWithAuthorization(
40+
address from,
41+
address to,
42+
uint256 value,
43+
uint256 validAfter,
44+
uint256 validBefore,
45+
bytes32 nonce,
46+
uint8 v,
47+
bytes32 r,
48+
bytes32 s
49+
) public virtual {
50+
_transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, v, r, s);
51+
}
52+
53+
/// @inheritdoc IERC3009
54+
function receiveWithAuthorization(
55+
address from,
56+
address to,
57+
uint256 value,
58+
uint256 validAfter,
59+
uint256 validBefore,
60+
bytes32 nonce,
61+
uint8 v,
62+
bytes32 r,
63+
bytes32 s
64+
) public virtual {
65+
_receiveWithAuthorization(from, to, value, validAfter, validBefore, nonce, v, r, s);
66+
}
67+
68+
/// @inheritdoc IERC3009Cancel
69+
function cancelAuthorization(address authorizer, bytes32 nonce, uint8 v, bytes32 r, bytes32 s) public virtual {
70+
_cancelAuthorization(authorizer, nonce, v, r, s);
71+
}
72+
73+
/// @dev Internal version of {transferWithAuthorization}.
74+
function _transferWithAuthorization(
75+
address from,
76+
address to,
77+
uint256 value,
78+
uint256 validAfter,
79+
uint256 validBefore,
80+
bytes32 nonce,
81+
uint8 v,
82+
bytes32 r,
83+
bytes32 s
84+
) internal virtual {
85+
_transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, abi.encodePacked(r, s, v));
86+
}
87+
88+
/// @dev Internal version of {receiveWithAuthorization} that accepts a packed signature.
89+
function _transferWithAuthorization(
90+
address from,
91+
address to,
92+
uint256 value,
93+
uint256 validAfter,
94+
uint256 validBefore,
95+
bytes32 nonce,
96+
bytes memory signature
97+
) internal virtual {
98+
require(
99+
block.timestamp > validAfter && block.timestamp < validBefore,
100+
ERC3009InvalidAuthorizationTime(validAfter, validBefore)
101+
);
102+
require(!_consumed[from][nonce], ERC3009ConsumedAuthorization(from, nonce));
103+
require(
104+
SignatureChecker.isValidSignatureNow(
105+
from,
106+
MessageHashUtils.toTypedDataHash(
107+
_domainSeparatorV4(),
108+
keccak256(
109+
abi.encode(
110+
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
111+
from,
112+
to,
113+
value,
114+
validAfter,
115+
validBefore,
116+
nonce
117+
)
118+
)
119+
),
120+
signature
121+
),
122+
ERC3009InvalidSignature()
123+
);
124+
125+
_consumed[from][nonce] = true;
126+
emit AuthorizationUsed(from, nonce);
127+
_transfer(from, to, value);
128+
}
129+
130+
/**
131+
* @dev Receive a transfer with a signed authorization from the payer
132+
*
133+
* This has an additional check to ensure that the payee's address
134+
* matches the caller of this function to prevent front-running attacks.
135+
*/
136+
function _receiveWithAuthorization(
137+
address from,
138+
address to,
139+
uint256 value,
140+
uint256 validAfter,
141+
uint256 validBefore,
142+
bytes32 nonce,
143+
uint8 v,
144+
bytes32 r,
145+
bytes32 s
146+
) internal virtual {
147+
_receiveWithAuthorization(from, to, value, validAfter, validBefore, nonce, abi.encodePacked(r, s, v));
148+
}
149+
150+
/// @dev Internal version of {receiveWithAuthorization} that accepts a packed signature.
151+
function _receiveWithAuthorization(
152+
address from,
153+
address to,
154+
uint256 value,
155+
uint256 validAfter,
156+
uint256 validBefore,
157+
bytes32 nonce,
158+
bytes memory signature
159+
) internal virtual {
160+
require(to == msg.sender, ERC20InvalidReceiver(to));
161+
require(
162+
block.timestamp > validAfter && block.timestamp < validBefore,
163+
ERC3009InvalidAuthorizationTime(validAfter, validBefore)
164+
);
165+
require(!_consumed[from][nonce], ERC3009ConsumedAuthorization(from, nonce));
166+
require(
167+
SignatureChecker.isValidSignatureNow(
168+
from,
169+
MessageHashUtils.toTypedDataHash(
170+
_domainSeparatorV4(),
171+
keccak256(
172+
abi.encode(RECEIVE_WITH_AUTHORIZATION_TYPEHASH, from, to, value, validAfter, validBefore, nonce)
173+
)
174+
),
175+
signature
176+
),
177+
ERC3009InvalidSignature()
178+
);
179+
180+
_consumed[from][nonce] = true;
181+
emit AuthorizationUsed(from, nonce);
182+
_transfer(from, to, value);
183+
}
184+
185+
/// @dev Internal version of {cancelAuthorization}/
186+
function _cancelAuthorization(address authorizer, bytes32 nonce, uint8 v, bytes32 r, bytes32 s) internal virtual {
187+
_cancelAuthorization(authorizer, nonce, abi.encodePacked(r, s, v));
188+
}
189+
190+
/// @dev Internal version of {cancelAuthorization} that accepts a packed signature.
191+
function _cancelAuthorization(address authorizer, bytes32 nonce, bytes memory signature) internal virtual {
192+
require(!_consumed[authorizer][nonce], ERC3009ConsumedAuthorization(authorizer, nonce));
193+
require(
194+
SignatureChecker.isValidSignatureNow(
195+
authorizer,
196+
MessageHashUtils.toTypedDataHash(
197+
_domainSeparatorV4(),
198+
keccak256(abi.encode(CANCEL_AUTHORIZATION_TYPEHASH, authorizer, nonce))
199+
),
200+
signature
201+
),
202+
ERC3009InvalidSignature()
203+
);
204+
205+
_consumed[authorizer][nonce] = true;
206+
emit AuthorizationCanceled(authorizer, nonce);
207+
}
208+
}

0 commit comments

Comments
 (0)