openzeppelin / cairo-contracts Goto Github PK
View Code? Open in Web Editor NEWOpenZeppelin Contracts written in Cairo for Starknet, a decentralized ZK Rollup
Home Page: https://docs.openzeppelin.com/contracts-cairo
License: MIT License
OpenZeppelin Contracts written in Cairo for Starknet, a decentralized ZK Rollup
Home Page: https://docs.openzeppelin.com/contracts-cairo
License: MIT License
Hey guys, I found a few bugs in the ERC20 contracts for Cairo:
get_caller_address
which resolves to 0 if a user calls the contract directlyIs there somebody working on this? I'm happy to step in and contribute a pull request to finish this contract if it's needed. I'm trying to build some DEX contracts on top of this and can't progress too far without a standardized token contract.
Another feature request regarding the extensibility of Starknet smart contracts. Probably further down the list of things needed right now but good to be cognizant of while moving forward. The OpenZeppelin's Solidity ERC20 uses _beforeTokenTransfer and _afterTokenTransfer functions allowing functionality such as ERC20Pausable.
StorageBuiltin
and storage_ptr
implicit args (they were merged into syscall_ptr
)syscall_ptr
implicit arg where necessary and use locals to prevent it from being revokedsend_transaction
to account for the new cairo-0.5.0 return values of call
and invoke
call
and invoke
Thanks to @RoboTeddy for providing a reference implementation in #39
The current implementation of the Account contract requires the nonce as argument even though it's not used in the function. In fact the current nonce is read from a storage variable current_nonce, hence there's no need to have nonce as a function parameter.
_burn()
Due to the uncertainty regarding how best to handle Starknet smart contract extensibility, the _burn()
function sits in an awkward position. The function is declared as internal
on OpenZeppelin's Solidity ERC20 base contract (ERC20.sol). In order to properly test the function's logic, an external
wrapper function should be used to invoke _burn()
from outside of the contract. The Solidity version created ERC20Mock.sol to test the base contractโtheir mock contract includes an external burn()
function. For this repo, the feature should try and follow the Solidity implementation of ERC20. An approach to consider would be creating an external burn()
function within contracts/ERC20.cairo
. This, of course, assumes we don't find a practical solution for extensibility in the very near future.
The _burn()
function should also include burner address protection as documented in issue #2 .
Originally posted by martriay December 14, 2021
I was thinking that maybe we could abstract ERC20 even further and move transferFrom
, increaseAllowance
, etc. implementations to the base contract, and require frontend contracts just to expose them instead of re-implementing them. What do you think?
Short string support should suffice.
The current ERC20 implementation does not implement any math overflow checks. One potential approach is to cap the the supply on the minting function, which would allow for any transfer to be automatically safe.
Usually, the way to burn a token is to send it to an address that no one controls. In Ethereum, this is usually the Zero Address or 0x0
. But in StarkNet, the get_caller_address()
function will return 0
if there's no caller address. This can happen if a transaction is sent to a contract directly instead of through an account contract.
To accommodate to expected behavior, a check must be set in place to prevent someone to directly call the transfer()
function on a token contract, impersonate the burner address, and break invariants.
To allow L1 bridged tokens to seamlessly integrate with this implementation, it should implement uint256
operations instead of felt
, which takes up 252 bytes.
Getting this error after 'nile install' when trying to follow the installation instructions on an m1 mac running Big Sur 11.5.2:
creating build/temp.macosx-10.14.6-arm64-3.8/src
clang -Wno-unused-result -Wsign-compare -Wunreachable-code -fno-common -dynamic -DNDEBUG -g -fwrapv -O3 -Wall -iwithsysroot/System/Library/Frameworks/System.framework/PrivateHeaders -iwithsysroot/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/Headers -arch arm64 -arch x86_64 -Werror=implicit-function-declaration -Isrc/ -I/Users/cipherGarden/Documents/cipherGarden/tutorials/starkware/zeppelinCairo/cairo-contracts/env/include -I/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/include/python3.8 -c src/curveMath.c -o build/temp.macosx-10.14.6-arm64-3.8/src/curveMath.o -O2
In file included from src/curveMath.c:1:
src/curveMath.h:6:10: fatal error: 'gmp.h' file not found
#include <gmp.h>
^~~~~~~
1 error generated.
error: command 'clang' failed with exit status 1
----------------------------------------
๐ข Code to reproduce bug
brew install gmp # mac
git clone https://github.com/OpenZeppelin/cairo-contracts.git
cd cairo-contracts
python3 -m venv env
source env/bin/activate
pip install cairo-nile
nile install
python version: 3.8.2
I have followed the README file and when I try to run tests with pytest
I get the following error:
(env) user@users-MBP cairo-contracts % pytest
========================================================= test session starts =========================================================
platform darwin -- Python 3.8.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/user/dev/cairo-contracts
plugins: web3-5.24.0, hypothesis-6.24.0, eth-brownie-1.17.1, xdist-1.34.0, forked-1.3.0
collected 0 items / 5 errors
=============================================================== ERRORS ================================================================
_______________________________________________ ERROR collecting tests/test_Account.py ________________________________________________
ImportError while importing test module '/Users/user/dev/cairo-contracts/tests/test_Account.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../.pyenv/versions/3.8.7/lib/python3.8/importlib/__init__.py:127: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
tests/test_Account.py:3: in <module>
from starkware.starknet.testing.starknet import Starknet
E ModuleNotFoundError: No module named 'starkware'
shouldn't nile install
take care of the dependencies?
We need to think, decide, and implement a code distribution mechanism. Most probably a pypi package.
ERC721 contract transfer function is probably wrong
๐ Details
Transfer function of ERC721 contract should change not just the balance but the owner of the token as well.
What I see is just increase receiver balance and decrease sender balance like ERC20
๐ข Code to reproduce bug
func _transfer{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr
}(sender: felt, recipient: felt, amount: felt):
# validate sender has enough funds
let (sender_balance) = balances.read(user=sender)
assert_nn_le(amount, sender_balance)
# substract from sender
balances.write(sender, sender_balance - amount)
# add to recipient
let (res) = balances.read(user=recipient)
balances.write(recipient, res + amount)
return ()
end
Take a look, it's probably wrong.
Check out relevant discussion: #27
There is no need to include the this
parameter into every call to execute
in L112 of Account.cairo and later check that it equals to the storage variable address
in L150.
I believe it would be cleaner (less arguments to execute
and less code overall) to simply read the value of address
and include it in the Message struct in L120, i.e.
let (_address) = address.read()
local message: Message = Message(to, selector, calldata, calldata_size=calldata_len, _address, nonce)
On L1 the verification of off-chain signatures is (unfortunately...) typically achieved via ecrecover
. This only works for EOA wallets. EIP-1271 was developed to verify the off-chain signatures of smart-contract wallets.
With Account Abstraction we need a generic interface to validate off-chain signatures and all accounts should implement that interface, i.e. we need to make sure it becomes a standard for Accounts.
I would suggest something similar to EIP-1271:
const MAGIC_VALUE = # get_selector_from_name('is_valid_signature')
@view
func is_valid_signature{
storage_ptr: Storage*,
pedersen_ptr: HashBuiltin*,
ecdsa_ptr: SignatureBuiltin*,
syscall_ptr: felt*,
range_check_ptr
} (
hash: felt,
signatures_len: felt,
signatures: Signature*
) -> (magic_value: felt):
# validate the signatures
# return MAGIC_VALUE if the signatures are correct
end
where the method can receive an array of signatures to support EOA type accounts as well as multisig type accounts.
Currently, the account contract does not verify that the nonce
in the transaction call data is equal to the current_nonce
in the contract, but only that the user signed on the current_nonce
. This allows a malicious sequencer to replace nonce
with an arbitrary felt. This results in a malleable vulnerability since the sequencer can change the transaction hash, while still being able to execute the transaction.
๐ Details
I suggest adding an assert in the execute()
function, that verifies that the nonce
in the call data is equal to the current_nonce
in the contract storage.
As stated by @lior-stark in #24, starkware.Cairo.common.hash_state
does the same as Account's custom hash_message
function but it's cheaper.
We should leverage that and reimplement Signer.py accordingly.
I am thinking there might be two ways to extend a contract, e.g., an ERC token to be Ownable.
myToken.cairo
with top-of-file import-style mechanism: from ozcc import ERC, Ownable
.ozcc generate ERC Ownable
that produces a populated myToken.cairo
.Option 1 would mimic existing Solidity experience, and would involve functions overriding others in a hierarchy.
Option 2 would involve the program fetching only non-overridden functions from the chosen combination and placing those in a file.
Both options are compatible with pre-generated common patterns (721, 721-ownable, 721-ownable-enumerable, 721-enumerable). Option 2 might result in a less-complex contract file, because there would be no override patterns for the developer to mentally manage.
eventually, when we figure it out:
Base token contracts, their interfaces and presets are all thrown into the same folder today. We should think of a better way to organize files, with a focus on discoverability and ease of import paths declaration.
Combining delegate_call and the upcoming fallback function.
This should be implemented in the is_valid_signature
function.
This is how it looks for single signature accounts:
@view
func is_valid_signature{
storage_ptr: Storage*,
pedersen_ptr: HashBuiltin*,
ecdsa_ptr: SignatureBuiltin*,
syscall_ptr: felt*,
range_check_ptr
} (
hash: felt,
signature_len: felt,
signature: felt*
) -> ():
assert_initialized()
let (_public_key) = public_key.read()
# This interface expects a signature pointer and length to make
# no assumption about signature validation schemes.
# But this implementation does, and it expects a (sig_r, sig_s) pair.
let sig_r = signature[0]
let sig_s = signature[1]
verify_ecdsa_signature(
message=hash,
public_key=_public_key,
signature_r=sig_r,
signature_s=sig_s)
return ()
end
Cairo's Uint256 library applies its own uint256_check()
on all arithmetic functions; nevertheless, this does not account for uint256 arguments themselves. Each uint256 value is really a struct of two 128bit felts. Since Cairo's felt size equates to 252 bits, external function arguments easily overflow prior to reaching any security checks. Suppose a function, to further illustrate, accepts a uint256 argument, and a user inserts (2^128 + 1, 2^128)
. The values will be interpreted as (1, 0)
.
uint256_check
In order to ensure uint256 arguments (of two 128bit felts) do not exceed 2^256 - 1, a check should be implemented in all external functions accepting uint256 arguments as well as _mint()
and _burn()
(when added). uint256_check()
should suffice from Cairo's Uint256.
transfer()
transfer_from()
approve()
increase_allowance()
decrease_allowance()
_mint()
_burn()
To prevent well known ERC20 approval issues, non-standard functions increaseAllowance
and decreaseAllowance
need to be implemented.
All snake_case
functions should be changed to camelCase
in order to match the standard put forth in EIP20. See the ERC20 function naming discussion.
The parameters and return value names should mirror those set in EIP20. For example:
@view
func balance_of{
...
}(account: felt) -> (res: Uint256):
...
end
res
should be changed to balance
.
Can't deploy simple ERC20 contract
No modification has been made to the contract
Quering the starknet network for the error I got:
filip@filip-pc:~$ starknet tx_status --hash 0x684194e5774d9c4b9d068cb84dbf9dc73ee19e0100528c9f09022044f760ca6 --network alpha-goerli --contract /path/to/my-project/starknet-artifacts/contracts/utils/token/ERC20.cairo/ERC20.json --error_message
/home/filip/.local/lib/python3.8/site-packages/starkware/starknet/common/storage.cairo:22:5: Error at pc=0:91:
if is_small != 0:
^^
Got an exception while executing a hint.
Cairo traceback (most recent call last):
/path/to/my-project/contracts/utils/token/ERC20.cairo:19:6
func constructor{
^*********^
/path/to/my-project/contracts/utils/token/ERC20.cairo:29:5
ERC20_initializer(name, symbol, initial_supply, recipient)
^********************************************************^
/path/to/my-project/contracts/utils/token/ERC20_base.cairo:54:5
ERC20_mint(recipient, initial_supply)
^***********************************^
/path/to/my-project/contracts/utils/token/ERC20_base.cairo:135:30
let (balance: Uint256) = ERC20_balances.read(account=recipient)
^************************************^
autogen/starknet/storage_var/ERC20_balances/impl.cairo:16:30
let (storage_addr) = addr(account)
^***********^
autogen/starknet/storage_var/ERC20_balances/impl.cairo:10:21
let (res) = normalize_address(addr=res)
^*************************^
Traceback (most recent call last):
File "<hint5>", line 5, in <module>
AssertionError: normalize_address() cannot be used with the current constants.
Problem: Buggy signing code or a malicious actor can prevent an Account contract from being used by submitting a StarkNet transaction that fails due to invalid signature. Because StarkNet Alpha v4 does not allow resubmitting/updating a previously submitted transaction hash, a failed contract invocation due to invalid signature can guarantee future failures, even for future properly signed calls with the same transaction hash (same nonce + parameters).
Potential Solutions: StarkNet should allow reprocessing of same transaction hash or the Account contract should support nonces > current_nonce
Steps to reproduce:
Workaround: After the failed invocation with the bad signature, Invoke a different contract with a correct signature, which forces the nonce to increment, and you can make a successful invocation with the newly updated nonce
Note: It is unclear to me if the problem can occur with a correct signature. For example, if Alice makes an invocation that fails for some other reason, like an assertion failing due to a contract state. Then say the state on the target contract is updated by Bob, which should result in a success should Alice attempt to make the same call again. Alice's invocation could fail because StarkNet Alpha v4 will not reprocess the transaction due to having the same transaction hash.
Background: https://www.cairo-lang.org/docs/hello_starknet/user_auth.html#what-if-we-have-an-invalid-signature
When I try sending execute method in Account contract, if the transaction is failed, the nonce is not update and I can't transfer again
I am trying to clone the repo and compile the contracts, and I follow the instructions.
An error pops up while I compile
contracts/Pausable_base.cairo:13:9: Unexpected implicit argument 'pedersen_ptr' in an external function.
pedersen_ptr: HashBuiltin*,
^**********^
contracts/token/ERC20_base.cairo:65:9: Unexpected implicit argument 'pedersen_ptr' in an external function.
pedersen_ptr : HashBuiltin*,
^**********^
contracts/token/ERC721.cairo:122:20: Unknown identifier 'total_supply'.
let (supply) = total_supply.read()
^***************^
The json artifacts are empty, and these files are not compiled.
๐ Details
Pretty much that's it
๐ข Code to reproduce bug
git clone https://github.com/OpenZeppelin/cairo-contracts
cd cairo-contracts
python3 -m venv env
source env/bin/activate
pip install cairo-nile
nile install
nile compile
execute
worksIAccount.cairo
interfaceSigner.py
When discussing #9 we sort of ignored that implementing tokenUri
requires a way to implement strings in Cairo. Strings are also used in ERC-20 (for the optional but so convenient symbol
and name
methods) and we have seen projects using them to generate dynamic NFT images (returning the NFT as svg).
In discord we mentioned that strings can be implemented by storing bytes in felts (251 bits or 31 bytes per felt). One issue is that, since StarkNet storage cannot store arrays, that's also the maximum string length that can be stored.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.