-
Notifications
You must be signed in to change notification settings - Fork 328
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
ERC20 Permit Component #1140
base: main
Are you sure you want to change the base?
ERC20 Permit Component #1140
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey Alejandro, the implementation is looking good, but I think we should refactor this into an extra embeddable implementation in the ERC20Component, instead of an extra component, since this would reduce the boilerplate in the final contract, and a component without storage members and events can be just an extra module.
/// signature (following SNIP-12 standard) used for ERC20Permit functionality. It's crucial that the | ||
/// SNIP12Metadata name remains unique to avoid confusion and potential security issues. | ||
/// | ||
/// For more complex or custom contracts, use Wizard for Cairo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will we have this release together with a Wizard update?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed ERC20Permit preset and this message also as a result
/// EIP-2612: https://eips.ethereum.org/EIPS/eip-2612 | ||
/// SNIP-12: https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md | ||
#[starknet::component] | ||
pub mod ERC20PermitComponent { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this extension doesn't need extra storage or events, it doesn't need to be a component, but can be implemented as an extra embeddable_impl in ERC20Component. This module can still exist as a container for helpers.
All we need is this extra impl in the ERC20Component (with the corresponding imports):
#[embeddable_as(ERC20PermitImpl)]
impl ERC20Permit<
TContractState,
+HasComponent<TContractState>,
+ERC20HooksTrait<TContractState>,
impl Nonces: NoncesComponent::HasComponent<TContractState>,
impl Metadata: SNIP12Metadata,
+Drop<TContractState>
> of IERC20Permit<ComponentState<TContractState>> {
/// Sets the allowance of the `spender` over `owner`'s tokens after validating the signature
/// generated off-chain and signed by the `owner`.
///
/// Requirements:
///
/// - `owner` is a deployed account contract.
/// - `spender` is not the zero address.
/// - `deadline` is a timestamp in the future.
/// - `signature` is a valid signature that can be validated with a call to `owner` account.
/// - `signature` must use the current nonce of the `owner`.
///
/// Emits an `Approval` event.
fn permit(
ref self: ComponentState<TContractState>,
owner: ContractAddress,
spender: ContractAddress,
amount: u256,
deadline: u64,
signature: Array<felt252>
) {
assert(get_block_timestamp() <= deadline, Errors::EXPIRED_SIGNATURE);
// Get current nonce and increment it
let mut nonces_component = get_dep_component_mut!(ref self, Nonces);
let nonce = nonces_component.use_nonce(owner);
// Compute hash for permit
let permit = Permit {
token: get_contract_address(), spender, amount, nonce, deadline
};
let permit_hash = permit.get_message_hash(owner);
// Make a call to the account to validate permit signature
let is_valid_sig_felt = DualCaseAccount { contract_address: owner }
.is_valid_signature(permit_hash, signature);
// Check the response is either 'VALID' or True (for backwards compatibility)
let is_valid_sig = is_valid_sig_felt == starknet::VALIDATED || is_valid_sig_felt == 1;
assert(is_valid_sig, Errors::INVALID_SIGNATURE);
// Approve
self._approve(owner, spender, amount);
}
/// Returns the current nonce of the `owner`. A nonce value must be
/// included whenever a signature for `permit` is generated.
fn nonces(self: @ComponentState<TContractState>, owner: ContractAddress) -> felt252 {
let nonces_component = get_dep_component!(self, Nonces);
nonces_component.nonces(owner)
}
/// Returns the domain separator used in generating a message hash for `permit` signature.
/// The domain hashing logic follows SNIP-12 standard.
fn DOMAIN_SEPARATOR(self: @ComponentState<TContractState>) -> felt252 {
let domain = StarknetDomain {
name: Metadata::name(),
version: Metadata::version(),
chain_id: get_tx_info().unbox().chain_id,
revision: 1
};
domain.hash_struct()
}
}
And then the preset can avoid the extra component boilerplate, and can just embed the impl (together with the NoncesComponent) when the permit logic is required.
As a general rule, we should avoid components without storage and events (and sometimes even with events), since they can be regular modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a general rule, we should avoid components without storage and events (and sometimes even with events), since they can be regular modules.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completely agree, refactored it to be a trait of the ERC20 component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, sir! I do agree with Eric with having ERC20Permit as an embeddable impl inside of ERC20Component. This should be a little easier to integrate into a contract. Aside from this, I left a few comments :)
/// EIP-2612: https://eips.ethereum.org/EIPS/eip-2612 | ||
/// SNIP-12: https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md | ||
#[starknet::component] | ||
pub mod ERC20PermitComponent { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a general rule, we should avoid components without storage and events (and sometimes even with events), since they can be regular modules.
+1
/// # ERC20Permit Preset | ||
/// | ||
/// The ERC20Permit preset integrates standard ERC20 token functionality with the ERC20Permit | ||
/// extension, as defined by EIP-2612. This preset allows for token approvals via off-chain | ||
/// signatures, thus enhancing transaction efficiency by reducing the need for on-chain approval | ||
/// transactions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want a preset for erc20permit? I don't see why we'd include this and not erc20votes (that's not to suggest we should have a preset for erc20votes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we did include this as a preset, it should be upgradeable and its interface should be defined in presets::src::interfaces
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decided to go without the preset. It doesn't make sense to have it, especially once it was refactored to be a trait instead of a component
pub(crate) impl SNIP12MetadataImpl of SNIP12Metadata { | ||
fn name() -> felt252 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need the pub(crate)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, now I import the implementation to use the same name
and version
in the test setup function
#[test] | ||
fn test_valid_permit_default_data() { | ||
let data = TEST_DATA(); | ||
let (owner, spender, amount, deadline) = (data.owner, data.spender, data.amount, data.deadline); | ||
let mock = setup(data); | ||
|
||
assert_valid_allowance(owner, spender, 0); | ||
|
||
let nonce = mock.nonces(owner); | ||
let signature = prepare_permit_signature(data, nonce); | ||
mock.permit(owner, spender, amount, deadline, signature); | ||
|
||
assert_valid_allowance(owner, spender, amount); | ||
assert_valid_nonce(owner, nonce + 1); | ||
} | ||
|
||
#[test] | ||
fn test_valid_permit_other_data() { | ||
let mut data = TEST_DATA(); | ||
data.spender = constants::OTHER(); | ||
data.amount = constants::TOKEN_VALUE_2; | ||
let (owner, spender, amount, deadline) = (data.owner, data.spender, data.amount, data.deadline); | ||
let mock = setup(data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the test bodies are the same, can't we get around writing them twice? Something like this:
fn valid_permit_assertions(data: TestData) {...}
#[test]
fn test_valid_permit_default_data() {
valid_permit_assertions(TEST_DATA());
}
Just an idea, not sure if it's worth it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Frankly, I don't think this would be helpful. There would be only 2 test cases covered by this helper, all other cases have slight differences
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1140 +/- ##
==========================================
+ Coverage 88.87% 88.95% +0.07%
==========================================
Files 57 58 +1
Lines 1375 1394 +19
==========================================
+ Hits 1222 1240 +18
- Misses 153 154 +1
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
Implements ERC20Permit extension
PR Checklist