Comments (7)
What do you mean "to save gas"?
from contracts-wizard.
In the process of a token holder changing allowance, approve
was the usual way to do it, but it requires a transaction, which costs gas. permit
also allows changing allowance, but only requires a signature, which costs no gas, so it's a better way. New tokens should have permit
as a more cost-effective way to approve allowance than approve
.
Dapps these days that work with tokens check whether permit
exists in the token, e.g. by looking for selector d505accf
. Only if permit
doesn't exist in a particular token then they fall back to calling approve
.
from contracts-wizard.
"Costs no gas" is not really accurate, but I see that the Wizard tooltip is claiming something similar. It should be cheaper than using approve because it's one less transaction, but there is a cost associated with cashing in the permit. It's overall cheaper to use infinite approvals, but it's arguably less secure.
I would definitely recommend including ERC20Permit in a token, but for UX reasons. I agree with making it a default in Wizard.
from contracts-wizard.
"Costs no gas" is not really accurate
Why do you say it's not really accurate? I've used signatures as a user of platforms like OpenSea and Rarible, as well as developed dapps that check for permit
in the token and calls it instead of calling approve
. On the UI instead of a transaction screen, some info about the signature request is shown (particularly the amount) and a "sign" button. After pressing it the allowance approval is done (without any transfer of funds), then a screen appears for the transfer transaction (which of course costs gas).
from contracts-wizard.
Generating the signature doesn't cost anything, but the transfer function will be slightly more expensive when it includes a permit, since it has to do additional work to validate it. Overall it will probably be cheaper than having a separate approve transaction.
from contracts-wizard.
The important point to note is that anyone can call permit
, and it's the caller that pays the gas fee. It doesn't matter who calls permit
, as long as a valid signature from the owner was obtained and presented when required, it just proceeds.
If the token owner calls permit
, it costs him gas.
If the spender calls permit
, it costs her gas, so then the approval process is free for the owner, which makes it much better for user experience.
Here's some of my code in which spender calls permit
:
const { ethers, network } = require(`hardhat`)
async function main() {
const [ownerWallet, spenderWallet] = await ethers.getSigners()
console.log(`Using network: ${network.name} (${network.config.chainId}), RPC url: ${network.config.url}`)
const tokenContractName = `TESTERC20`
const contractAddress = `0xc45Eb76AB6A29B64FD17A0F49e214a1f0a20A59D` // localhost
const contractOfOwner = await ethers.getContractAt(tokenContractName, contractAddress, ownerWallet)
const contractOfSpender = await ethers.getContractAt(tokenContractName, contractAddress, spenderWallet)
let txRec
const totalSupply = ethers.formatUnits(await contractOfOwner.totalSupply())
console.log(`Initially:`)
console.log(`owner has ${ethers.formatUnits(await contractOfOwner.balanceOf(ownerWallet.address))} of ${totalSupply} tokens`)
console.log(`spender has ${ethers.formatUnits(await contractOfOwner.balanceOf(spenderWallet.address))} of ${totalSupply} tokens`)
console.log(`owner has allowed spender to spend ${ethers.formatUnits(await contractOfOwner.allowance(ownerWallet.address, spenderWallet.address))} tokens`)
console.log(`owner has ${ethers.formatUnits(await ethers.provider.getBalance(ownerWallet.address), `ether`)} of native currency`)
console.log(`spender has ${ethers.formatUnits(await ethers.provider.getBalance(spenderWallet.address), `ether`)} of native currency`)
const amountAllowedToSpend = ethers.parseUnits(`2`, `ether`)
if (tokenHasPermit(await contractOfOwner.getDeployedCode())) {
const deadline = Math.floor(Date.now() / 1000 + 3600)
const splitSigAsArray = await getPermitSignature(contractOfOwner, ownerWallet, spenderWallet, amountAllowedToSpend, deadline) // ownerWallet does the signing in here to permit spender to spend his tokens
console.log(`Owner has given his signature`)
console.log(`Spender is now calling token's permit function with owner's signature so that she can spend his tokens...`)
txRec = await contractOfSpender.permit( // any account can call the permit function, as long as there's a signature (values in splitSigAsArray) that proves that owner has permitted his tokens to be spent by spender. Gas is paid by permit function caller, so approval process is free for owner if he didn't call permit.
ownerWallet.address,
spenderWallet.address,
amountAllowedToSpend,
deadline,
...splitSigAsArray
)
} else {
console.log(`Owner is calling token's approve function...`)
txRec = await contractOfOwner.approve(spenderWallet.address, amountAllowedToSpend) // only owner can call approve to let spender spend his tokens. contractOfSpender.approve doesn't work.
}
await txRec.wait()
console.log(`After permit / approve:`)
console.log(`owner has allowed spender to spend ${ethers.formatUnits(await contractOfOwner.allowance(ownerWallet.address, spenderWallet.address))} tokens`)
console.log(`owner has ${ethers.formatUnits(await ethers.provider.getBalance(ownerWallet.address), `ether`)} of native currency`)
console.log(`spender has ${ethers.formatUnits(await ethers.provider.getBalance(spenderWallet.address), `ether`)} of native currency`)
txRec = await contractOfSpender.transferFrom(ownerWallet.address, spenderWallet.address, amountAllowedToSpend) // spender calls transferFrom to transfer tokens from owner
await txRec.wait()
console.log(`After transferFrom:`)
console.log(`spender took ${ethers.formatUnits(amountAllowedToSpend)} tokens from owner`)
console.log(`owner has ${ethers.formatUnits(await contractOfOwner.balanceOf(ownerWallet.address))} of ${totalSupply} tokens`)
console.log(`spender has ${ethers.formatUnits(await contractOfOwner.balanceOf(spenderWallet.address))} of ${totalSupply} tokens`)
}
const getPermitSignature = async (tokenContract, ownerWallet, spenderWallet, value, deadline) => { // owner allows spender to spend value wei tokens before deadline
const [name, nonce] = await Promise.all([
tokenContract.name(),
tokenContract.nonces(ownerWallet.address),
])
const domain = {
name,
version: `1`,
chainId: network.config.chainId,
verifyingContract: tokenContract.target
}
// console.log(`domain: ${JSON.stringify(domain)}`)
const permitTypes = {
Permit: [{
name: `owner`,
type: `address`
},
{
name: `spender`,
type: `address`
},
{
name: `value`,
type: `uint256`
},
{
name: `nonce`,
type: `uint256`
},
{
name: `deadline`,
type: `uint256`
},
],
}
const permitValues = {
owner: ownerWallet.address,
spender: spenderWallet.address,
value,
nonce,
deadline,
}
// console.log(`permitValues: ${JSON.stringify(permitValues)}`)
const signature = await ownerWallet.signTypedData(domain, permitTypes, permitValues) // owner signs the data
const { v, r, s } = ethers.Signature.from(signature)
// const recoveredAddress = ethers.verifyTypedData(
// domain,
// permitTypes,
// permitValues,
// { v, r, s }
// )
// console.log(`recoveredAddress: ${recoveredAddress}. Verification ${recoveredAddress === ownerWallet.address ? "passed" : "failed"}.`)
return [v, r, s]
}
const tokenHasPermit = bytecode => bytecode.includes(`d505accf`) && bytecode.includes(`7ecebe00`) // permit & nonces selectors
main().catch(error => {
console.error(error)
process.exitCode = 1
})
Here's the output, showing no change in owner's native currency balance:
Using network: localhost (31337), RPC url: http://127.0.0.1:8545 Initially: owner has 100.0 of 1000.0 tokens spender has 0.0 of 1000.0 tokens owner has allowed spender to spend 0.0 tokens owner has 9999.9614911964478728 of native currency spender has 10000.0 of native currency Owner has given his signature Spender is now calling token's permit function with owner's signature so that she can spend his tokens... After permit / approve: owner has allowed spender to spend 2.0 tokens owner has 9999.9614911964478728 of native currency spender has 9999.999880784353054822 of native currency After transferFrom: spender took 2.0 tokens from owner owner has 98.0 of 1000.0 tokens spender has 2.0 of 1000.0 tokens Done in 8.48s.
from contracts-wizard.
I hope this will be done in v5.0, at https://docs.openzeppelin.com/contracts/5.x-rc/wizard and https://wizard.openzeppelin.com.
from contracts-wizard.
Related Issues (20)
- Can't resolve '../openzeppelin-contracts' in `wizard` 0.2.2 HOT 2
- ERC20 Premint field has no upper bound
- Invalid contracts generated for @openzeppelin/contracts 4.9.0 HOT 1
- Use package entry points for core
- Use token-specific pausable extensions HOT 1
- add required OpenZeppelin Contracts version as a comment ? HOT 1
- Copy to Clipboard not working from docs site HOT 1
- Use Cairo specific highlighting for Cairo 1+
- Provide better feedback in UI when input is invalid
- Add Cairo lang and OZ Contracts versions to generated contracts HOT 4
- [Vanta] Remediate "Low vulnerabilities identified in packages are addressed (Github Repo)" for npm-undici/CVE-2024-24758 HOT 1
- [Vanta] Remediate "Low vulnerabilities identified in packages are addressed (Github Repo)" for npm-undici/CVE-2024-24758 HOT 1
- [Vanta] Remediate "Low vulnerabilities identified in packages are addressed (Github Repo)" for npm-undici/CVE-2024-24758 HOT 1
- Support mixins
- Consider making upgradeability opt out HOT 2
- Support older version of the contracts library. HOT 1
- Support Contracts for Cairo v0.10.0
- Use Hardhat Ignition in downloaded Hardhat development package
- Used named imports for Solidity HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from contracts-wizard.