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

Feature/mint and call #138

Closed
wants to merge 76 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
cd1dd44
add first demo
malteish Apr 28, 2023
aae4eed
Prettified Code!
malteish Apr 28, 2023
f3ef099
transfer instead of transferFrom, add try catch
malteish May 2, 2023
a4041dc
fix test
malteish May 2, 2023
994b767
Bump decode-uri-component from 0.2.0 to 0.2.2
dependabot[bot] May 8, 2023
477b3dc
Bump cookiejar from 2.1.3 to 2.1.4
dependabot[bot] May 8, 2023
eb193a8
natSpec for Token
malteish May 2, 2023
9be8005
natSpec for ContinuousFundraising
malteish May 2, 2023
2cf81d9
natSpec for PersonalInvite
malteish May 2, 2023
21411f8
more natSpec for PersonalInvite
malteish May 2, 2023
ada3c80
natSpec for PersonalInviteFactory
malteish May 2, 2023
27e060a
natSpec for AllowList
malteish May 3, 2023
36a9662
natSpec for FeeSettings
malteish May 3, 2023
ff48a5d
more natSpec
malteish May 3, 2023
cc31cc5
typo
malteish May 3, 2023
e2350da
refactor ERC20 helper
malteish May 3, 2023
0a81777
test permit for EUROC
malteish May 3, 2023
0e6d24d
Prettified Code!
malteish May 3, 2023
448304a
test USDC
malteish May 3, 2023
8facdd0
Prettified Code!
malteish May 3, 2023
b83f5e3
add permit for DAI
malteish May 3, 2023
636aae8
Prettified Code!
malteish May 3, 2023
945835d
cleanup
malteish May 3, 2023
3b8f8b6
add basic test for VestingWallet
malteish May 5, 2023
ad7d5df
add test of timeLock with PersonalInvite
malteish May 5, 2023
d0860ff
fix test
malteish May 5, 2023
feb1a8b
remove workspace file
malteish May 5, 2023
7832e2a
Bump @openzeppelin/contracts from 4.8.0 to 4.8.3
dependabot[bot] May 8, 2023
bcdc4e8
test AllowList Events
malteish May 8, 2023
6c0938d
test ContinuousFundraising events
malteish May 8, 2023
e11b29e
test FeeSettings events
malteish May 8, 2023
7513e4a
test PersonalInvite event
malteish May 8, 2023
20caf4b
test PersonalInviteFactory events
malteish May 8, 2023
8097aab
test various salts
malteish May 8, 2023
b59b198
test Token events
malteish May 8, 2023
ec3d699
bump prettier solidity version
malteish May 8, 2023
8230597
run prettier
malteish May 8, 2023
0b78b95
change prettier config, reformat all files
malteish May 8, 2023
9987163
Prettified Code!
malteish May 8, 2023
2a57161
replace literal uint256max
malteish May 8, 2023
932c0bd
Prettified Code!
malteish May 9, 2023
a2fdff1
Merge branch 'develop' into feature/mintAndCall
malteish Aug 11, 2023
2599829
improve docs
malteish Aug 11, 2023
04959c4
add calculateBuyAmount function to fundraising
malteish Aug 11, 2023
f63397d
improve test
malteish Aug 11, 2023
048fe2c
add test for stealing funds
malteish Aug 11, 2023
eb04770
docs
malteish Aug 11, 2023
1646efe
limit onTransferReceived to payment token contract
malteish Aug 11, 2023
e0f7638
add ERC1271 to wallet
malteish Aug 14, 2023
7c7c713
prepare test
malteish Aug 14, 2023
3909ce3
fix empty test
malteish Sep 13, 2023
52a23a8
update docs on contract verification
malteish Aug 22, 2023
a40d418
add link
malteish Aug 22, 2023
dd8c827
add upgradeable contracts and token clone factory
malteish Aug 17, 2023
8680618
fix dependencies, add TokenCloneFactory tests
malteish Aug 17, 2023
17d2746
use token params as salt, update tests
malteish Aug 17, 2023
fe34d04
update tests
malteish Aug 17, 2023
924325d
update tests
malteish Aug 17, 2023
e6823e3
update tests
malteish Aug 17, 2023
d136927
fix token tests
malteish Aug 22, 2023
8b537b2
fix tests
malteish Aug 22, 2023
8f073b2
fix tests
malteish Aug 22, 2023
bde009e
fix test
malteish Aug 23, 2023
ff212a8
add salt
malteish Aug 23, 2023
b25c2ad
fix test
malteish Aug 23, 2023
1849765
fix test
malteish Aug 23, 2023
3cf5805
update hardhat
malteish Sep 1, 2023
d5a8c43
add clean command
malteish Sep 1, 2023
0d9f5ea
update openzeppelin
malteish Sep 1, 2023
f8a3e4f
update typescript and node types
malteish Sep 1, 2023
c1ec281
update hardhat packages
malteish Sep 1, 2023
4d74277
update ethers
malteish Sep 1, 2023
aa32fe2
update chai
malteish Sep 1, 2023
5574024
update eslint
malteish Sep 1, 2023
9e99e13
update solhint and typechain
malteish Sep 1, 2023
3d6bba2
update import and test
malteish Sep 13, 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
27 changes: 27 additions & 0 deletions contracts/CloneFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "@openzeppelin/contracts/proxy/Clones.sol";

abstract contract CloneFactory {
event NewClone(address clone);

/// The address of the implementation to clone
address immutable implementation;

constructor(address _implementation) {
require(_implementation != address(0), "CloneFactory: implementation can not be zero");
implementation = _implementation;
}

/**
* @notice Predicts the address of a clone that will be created
* @param salt The salt used to deterministically generate the clone address
* @return The address of the clone that will be created
* @dev This function does not check if the clone has already been created
*/
function predictCloneAddress(bytes32 salt) public view returns (address) {
return Clones.predictDeterministicAddress(implementation, salt);
}
}
9 changes: 9 additions & 0 deletions contracts/ContinuousFundraising.sol
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ contract ContinuousFundraising is ERC2771Context, Ownable2Step, Pausable, Reentr
emit TokensBought(_msgSender(), _amount, currencyAmount);
}

/**
* Calculate how many tokens can be bought with currencyAmount
* @param currencyAmount amount of currency to be used to buy tokens
* @return amount of tokens that can be bought with currencyAmount
*/
function calculateBuyAmount(uint256 currencyAmount) external view returns (uint256) {
return Math.mulDiv(currencyAmount, 10 ** token.decimals(), tokenPrice, Math.Rounding.Zero); // rounding down
}

/**
* @notice change the currencyReceiver to `_currencyReceiver`
* @param _currencyReceiver new currencyReceiver
Expand Down
1 change: 1 addition & 0 deletions contracts/PersonalInvite.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity 0.8.17;

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "./Token.sol";

/**
Expand Down
33 changes: 20 additions & 13 deletions contracts/Token.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/metatx/ERC2771ContextUpgradeable.sol";
import "./AllowList.sol";
import "./interfaces/IFeeSettings.sol";

Expand All @@ -21,7 +21,7 @@ import "./interfaces/IFeeSettings.sol";
*
* @dev The contract inherits from ERC2771Context in order to be usable with Gas Station Network (GSN) https://docs.opengsn.org/faq/troubleshooting.html#my-contract-is-using-openzeppelin-how-do-i-add-gsn-support and meta-transactions.
*/
contract Token is ERC2771Context, ERC20Permit, Pausable, AccessControl {
contract Token is ERC2771ContextUpgradeable, ERC20PermitUpgradeable, PausableUpgradeable, AccessControlUpgradeable {
/// @notice The role that has the ability to define which requirements an address must satisfy to receive tokens
bytes32 public constant REQUIREMENT_ROLE = keccak256("REQUIREMENT_ROLE");
/// @notice The role that has the ability to grant minting allowances
Expand Down Expand Up @@ -92,24 +92,28 @@ contract Token is ERC2771Context, ERC20Permit, Pausable, AccessControl {
event MintingAllowanceChanged(address indexed minter, uint256 newAllowance);

/**
* @notice Constructor for the token.
* @notice Constructor a token logic contract that can be used by clones. It does not initialize state variables properly, so the resulting contract will not be functional.
* @param _trustedForwarder trusted forwarder for the ERC2771Context constructor - used for meta-transactions. OpenGSN v2 Forwarder should be used.
*/
constructor(address _trustedForwarder) initializer ERC2771ContextUpgradeable(_trustedForwarder) {}

/**
* @notice Constructor for the token.
* @param _feeSettings fee settings contract that determines the fee for minting tokens
* @param _admin address of the admin. Admin will initially have all roles and can grant roles to other addresses.
* @param _name name of the specific token, e.g. "MyGmbH Token"
* @param _symbol symbol of the token, e.g. "MGT"
* @param _allowList allowList contract that defines which addresses satisfy which requirements
* @param _requirements requirements an address has to meet for sending or receiving tokens
*/
constructor(
address _trustedForwarder,
function initialize(
IFeeSettingsV1 _feeSettings,
address _admin,
AllowList _allowList,
uint256 _requirements,
string memory _name,
string memory _symbol
) ERC2771Context(_trustedForwarder) ERC20Permit(_name) ERC20(_name, _symbol) {
) public initializer {
// Grant admin roles
_grantRole(DEFAULT_ADMIN_ROLE, _admin); // except for the Transferer role, the _admin is the roles admin for all other roles
_setRoleAdmin(TRANSFERER_ROLE, TRANSFERERADMIN_ROLE);
Expand All @@ -131,6 +135,9 @@ contract Token is ERC2771Context, ERC20Permit, Pausable, AccessControl {

// set requirements (can be 0 to allow everyone to send and receive tokens)
requirements = _requirements;

__ERC20Permit_init(_name);
__ERC20_init(_name, _symbol);
}

/**
Expand Down Expand Up @@ -308,14 +315,14 @@ contract Token is ERC2771Context, ERC20Permit, Pausable, AccessControl {
/**
* @dev both ERC20Pausable and ERC2771Context have a _msgSender() function, so we need to override and select which one to use.
*/
function _msgSender() internal view override(Context, ERC2771Context) returns (address) {
return ERC2771Context._msgSender();
function _msgSender() internal view override(ContextUpgradeable, ERC2771ContextUpgradeable) returns (address) {
return ERC2771ContextUpgradeable._msgSender();
}

/**
* @dev both ERC20Pausable and ERC2771Context have a _msgData() function, so we need to override and select which one to use.
*/
function _msgData() internal view override(Context, ERC2771Context) returns (bytes calldata) {
return ERC2771Context._msgData();
function _msgData() internal view override(ContextUpgradeable, ERC2771ContextUpgradeable) returns (bytes calldata) {
return ERC2771ContextUpgradeable._msgData();
}
}
66 changes: 66 additions & 0 deletions contracts/TokenCloneFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./Token.sol";
import "./CloneFactory.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";

contract TokenCloneFactory is CloneFactory {
constructor(address _implementation) CloneFactory(_implementation) {}

function createTokenClone(
bytes32 _rawSalt,
address _trustedForwarder,
IFeeSettingsV1 _feeSettings,
address _admin,
AllowList _allowList,
uint256 _requirements,
string memory _name,
string memory _symbol
) external returns (address) {
bytes32 salt = keccak256(
abi.encodePacked(
_rawSalt,
_trustedForwarder,
_feeSettings,
_admin,
_allowList,
_requirements,
_name,
_symbol
)
);
address clone = Clones.cloneDeterministic(implementation, salt);
Token cloneToken = Token(clone);
require(cloneToken.isTrustedForwarder(_trustedForwarder), "TokenCloneFactory: Unexpected trustedForwarder");
cloneToken.initialize(_feeSettings, _admin, _allowList, _requirements, _name, _symbol);
emit NewClone(clone);
return clone;
}

function predictCloneAddress(
bytes32 _rawSalt,
address _trustedForwarder,
IFeeSettingsV1 _feeSettings,
address _admin,
AllowList _allowList,
uint256 _requirements,
string memory _name,
string memory _symbol
) external view returns (address) {
bytes32 salt = keccak256(
abi.encodePacked(
_rawSalt,
_trustedForwarder,
_feeSettings,
_admin,
_allowList,
_requirements,
_name,
_symbol
)
);
return Clones.predictDeterministicAddress(implementation, salt);
}
}
116 changes: 116 additions & 0 deletions contracts/Wallet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;

import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./interfaces/ERC1363.sol";
import "./ContinuousFundraising.sol";

/**
* @title Wallet
* @notice A wallet contract that can react to receiving ERC20 payment tokens according to the ERC1363 standard.
* The owner can associate a hash that will be inlcuded in the data field of the onTokenTransfer call
* with an address. When the wallet receives payment tokens with this hash in the data field, it will buy
* company tokens from the company's fundraising contract and send them to the address associated with the hash.
* @dev This implementation of the ERC1363Receiver interface is not fully compliant with the standard, because
* we never revert after onTransferReceived. See function details for more information.
*/
contract Wallet is Ownable2Step, ERC1363Receiver, IERC1271 {
using SafeERC20 for IERC20;

/**
* @notice stores the receiving address for each IBAN hash
*/
mapping(bytes32 => address) public receiverAddress;

ContinuousFundraising public fundraising;

event Set(bytes32 indexed ibanHash, address tokenReceiver);

constructor(ContinuousFundraising _fundraising) Ownable2Step() {
fundraising = _fundraising;
}

/**
* @notice sets (or updates) the receiving address for an IBAN hash
* @param _ibanHash the hash of the IBAN to set the receiving address for
* @param _tokenReceiver the address to send the tokens to
*/
function set(bytes32 _ibanHash, address _tokenReceiver) external onlyOwner {
receiverAddress[_ibanHash] = _tokenReceiver;
malteish marked this conversation as resolved.
Show resolved Hide resolved
emit Set(_ibanHash, _tokenReceiver);
}

/**
* @notice ERC1363 callback
* @dev To support the mintAndCall standard, this function MUST NOT revert! It deviates from the ERC1363
* standard in this regard. The intended use of monerium tokens, which are minted after a SEPA transfer,
* makes this necessary. If the intended action of buying tokens fails, the payment tokens remain in balance
* of the wallet contract. The owner can withdraw them later.
* @param operator The address which called `transferAndCall` or `transferFromAndCall` function
* @param from The address which are token transferred from
* @param value The amount of tokens transferred
* @return 0x600D600D on success, 0x0BAD0BAD if the buy failed, 0xDEADD00D if the receiver is not registered
*/
function onTransferReceived(
malteish marked this conversation as resolved.
Show resolved Hide resolved
address operator,
address from,
uint256 value,
bytes memory data
) external override returns (bytes4) {
bytes32 ibanHash = abi.decode(data, (bytes32));
if (receiverAddress[ibanHash] == address(0)) {
return 0xDEADD00D; // DEAD DOOD: ReceiverNotRegistered
}
IERC20 paymentCurrency = fundraising.currency();
if (_msgSender() != address(paymentCurrency)) {
return 0x4B1D4B1D; // FORBID FORBID: OperatorNotPaymentCurrency
}
uint256 amount = fundraising.calculateBuyAmount(value);
// grant allowance to fundraising
paymentCurrency.approve(address(fundraising), amount);
// try buying tokens https://solidity-by-example.org/try-catch/
try fundraising.buy(amount, receiverAddress[ibanHash]) {
return 0x600D600D; // ReceiverSuccess
} catch {
return 0x0BAD0BAD; // ReceiverFailure
}
}

/**
* @notice withdraws tokens to a given address
* @param token the token to withdraw
* @param to the address to send the tokens to
* @param amount the amount of tokens to send
*/
function withdraw(address token, address to, uint256 amount) external onlyOwner {
IERC20(token).safeTransfer(to, amount);
}

/**
* @notice withdraws ETH to a given address
* @param to the address to send the ETH to
* @param amount the amount of ETH to send
*/
function withdraw(address payable to, uint256 amount) external onlyOwner {
to.transfer(amount);
}

/**
* @notice enable signing of messages in the name of the contract using the owner's private key
* @dev Messages signed by an owner will be valid without a deadline. However, once the contract's owner changes, all old signatures will be invalid.
* @param _hash the hash of the data to be signed
* @param _signature the signature to be checked
* @return 0x1626ba7e
*/
function isValidSignature(bytes32 _hash, bytes memory _signature) public view override returns (bytes4) {
if (owner() == address(0)) {
// in case the ownership if the contract has been renounced, nobody should be able to sign in its name anymore
return bytes4(0);
}
return ECDSA.recover(_hash, _signature) == owner() ? this.isValidSignature.selector : bytes4(0);
}
}
14 changes: 14 additions & 0 deletions contracts/interfaces/ERC1363.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;

/**
@notice ERC1363 interface, see https://eips.ethereum.org/EIPS/eip-1363
*/
interface ERC1363Receiver {
function onTransferReceived(
address operator,
address from,
uint256 value,
bytes memory data
) external returns (bytes4);
}
36 changes: 29 additions & 7 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,24 @@ The company-related contracts are:

They are deployed through the web app.

As long as automatic verification is not implemented, the contracts need to be verified manually. Doing this with foundry was not successful, possibly because they are compiled using hardhat. The ContinuousFundraising contract can be verified like this:
For development purposes, the contracts can be deployed like this:

```bash
forge script script/DeployToken.s.sol --rpc-url $GOERLI_RPC_URL --verify --broadcast
```

## Forwarder

If the forwarder has not been deployed yet, e.g. when working in a testing environment, it can be deployed like this:
`forge create node_modules/@opengsn/contracts/src/forwarder/Forwarder.sol:Forwarder --private-key $PRIVATE_KEY --rpc-url $GOERLI_RPC_URL --verify --etherscan-api-key $ETHERSCAN_API_KEY`

## Contract Verification

As long as automatic verification is not implemented, the contracts need to be verified manually. This can be done using hardhat or foundry. Sometimes one or the other works better.

### hardhat

The ContinuousFundraising contract can be verified like this:

```
yarn hardhat verify --network goerli 0x29b659E948616815FADCD013f6BfC767da1BDe83 0x0445d09A1917196E1DC12EdB7334C70c1FfB1623 0xA1e28D1f17b7Da62d10fbFaFCA98Fa406D759ce2 10000000000000000000 50000000000000000000 1000000 100000000000000000000 0x07865c6E87B9F70255377e024ace6630C1Eaa37F 0xc1C74cbD565D16E0cCe9C5DCf7683368DE4E35e2
Expand Down Expand Up @@ -79,13 +96,18 @@ optimizer: {
},
```

For development purposes, the contracts can be deployed like this:
### foundry

```bash
forge script script/DeployToken.s.sol --rpc-url $GOERLI_RPC_URL --verify --broadcast
Example for token verification:

```
forge verify-contract 0x458A75E83c50080279e8d8e870cF0d0F4B48C01b --constructor-args-path verificationArguments/foundry/Token --chain goerli Token
```

## Forwarder
Provide the constructor arguments separated by whitespace in a file like this:

If the forwarder has not been deployed yet, e.g. when working in a testing environment, it can be deployed like this:
`forge create node_modules/@opengsn/contracts/src/forwarder/Forwarder.sol:Forwarder --private-key $PRIVATE_KEY --rpc-url $GOERLI_RPC_URL --verify --etherscan-api-key $ETHERSCAN_API_KEY`
```
0x0445d09A1917196E1DC12EdB7334C70c1FfB1623 0x387aD1Aa745C70829b651B3F2D3E7852Df961C93 0x2Db0DD9394f851baefD1FA3334c6B188A0C0548D 0x274ca5f21Cdde06B6E4Fe063f5087EB6Cf3eAe55 0 'Max Mustermann Token' 'MAXMT'
```

More info can be found [here](https://book.getfoundry.sh/reference/forge/forge-verify-contract).
Loading
Loading