-
Notifications
You must be signed in to change notification settings - Fork 0
ERC20Supra Smart Contract #6
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
Changes from all commits
6c1f3ce
c5e6b37
e286a73
1e11d5e
69db948
5a1d6dc
3c3ecc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.27; | ||
|
|
||
| import {Script, console} from "forge-std/Script.sol"; | ||
| import {ERC20Supra} from "../src/ERC20Supra.sol"; | ||
|
|
||
| contract DeployERC20Supra is Script { | ||
| address owner; | ||
|
|
||
| function setUp() public { | ||
| owner = vm.envAddress("OWNER"); | ||
| } | ||
|
|
||
| function run() public { | ||
| vm.startBroadcast(); | ||
|
|
||
| // Deploy ERC20Supra | ||
| ERC20Supra erc20Supra = new ERC20Supra(owner); | ||
| console.log("ERC20Supra deployed at: ", address(erc20Supra)); | ||
|
|
||
| vm.stopBroadcast(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.27; | ||
|
|
||
| import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
| import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; | ||
| import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; | ||
| import "@openzeppelin/contracts/access/Ownable2Step.sol"; | ||
|
|
||
| contract ERC20Supra is ERC20, ERC20Burnable, Ownable2Step, ERC20Permit { | ||
|
|
||
| /// @notice Error thrown if user has insufficient balance. | ||
| error InsufficientBalance(); | ||
| /// @notice Error thrown if 0 is passed as amount. | ||
| error InvalidAmount(); | ||
| /// @notice Error thrown if tokens are sent to the token contract itself. | ||
| error InvalidTransfer(); | ||
| /// @notice Error thrown if low level call fails. | ||
| error TransferFailed(); | ||
|
|
||
| /// @notice Emitted when native tokens are deposited to mint and receive ERC20Supra tokens. | ||
| /// @param account Address of the depositer. | ||
| /// @param amount Amount deposited. | ||
| event NativeToERC20Supra(address indexed account, uint256 indexed amount); | ||
|
|
||
| /// @notice Emitted when native tokens are withdrawn by burning ERC20Supra tokens. | ||
| /// @param account Address withdrawing. | ||
| /// @param amount Amount withdrawn. | ||
| event ERC20SupraToNative(address indexed account, uint256 indexed amount); | ||
|
|
||
| constructor(address _initialOwner) | ||
| ERC20("ERC20Supra", "SUPRA") | ||
| Ownable(_initialOwner) | ||
| ERC20Permit("ERC20Supra") | ||
| {} | ||
|
|
||
| /// @notice Deposit native token → Mint ERC20Supra 1:1 | ||
| function nativeToErc20Supra() external payable { | ||
| if (msg.value == 0) revert InvalidAmount(); | ||
| _mint(msg.sender, msg.value); | ||
|
Comment on lines
+38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since, it's a |
||
|
|
||
| emit NativeToERC20Supra(msg.sender, msg.value); | ||
| } | ||
|
|
||
| /// @notice Withdraw native token → Burn ERC20Supra 1:1 | ||
| /// @param _amount Amount of native tokens to withdraw. | ||
| function erc20SupraToNative(uint256 _amount) external { | ||
| if (_amount == 0) revert InvalidAmount(); | ||
| if (balanceOf(msg.sender) < _amount) revert InsufficientBalance(); | ||
|
|
||
| _burn(msg.sender, _amount); | ||
| emit ERC20SupraToNative(msg.sender, _amount); | ||
|
|
||
| (bool sent, ) = payable(msg.sender).call{value: _amount}(""); | ||
| if (!sent) revert TransferFailed(); | ||
| } | ||
|
|
||
| /// @notice Allows a user to send native tokens directly and get ERC20Supra. | ||
| receive() external payable { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what are the use-cases of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @aregng my understanding is that this is not automation specific. A user can send native token to a contract via native value transfer, which will result in invocation of
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, if an EOA sends native token directly to smart contract address There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not have any particular opinion on this , so let's keep both unless there is a valid reason not to have any of them. |
||
| if (msg.value == 0) revert InvalidAmount(); | ||
|
|
||
| _mint(msg.sender, msg.value); | ||
| emit NativeToERC20Supra(msg.sender, msg.value); | ||
| } | ||
|
|
||
| /// @notice Disallows sending tokens to the token contract itself. This prevents accidental locking of tokens. | ||
| function _update(address _from, address _to, uint256 _value) internal override { | ||
| if (_to == address(this)) revert InvalidTransfer(); | ||
| super._update(_from, _to, _value); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.27; | ||
|
|
||
| import {Test} from "forge-std/Test.sol"; | ||
| import {ERC20Supra} from "../src/ERC20Supra.sol"; | ||
|
|
||
| contract ERC20SupraTest is Test { | ||
| ERC20Supra token; | ||
|
|
||
| address owner = address(0x123); | ||
| address alice = address(0x456); | ||
| address bob = address(0x789); | ||
|
|
||
| function setUp() public { | ||
| vm.deal(alice, 100 ether); | ||
| vm.deal(bob, 50 ether); | ||
| vm.deal(owner, 10 ether); | ||
|
|
||
| token = new ERC20Supra(owner); | ||
| } | ||
|
|
||
| function testDeployment() public view { | ||
| assertEq(token.owner(), owner); | ||
| assertEq(token.name(), "ERC20Supra"); | ||
| assertEq(token.symbol(), "SUPRA"); | ||
| assertEq(token.decimals(), 18); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @so-schen , do we have 18 decimal precision for native $SUPRA on EVM? (CC: @udityadav-supraoracles @aregng ) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes , according to the following comment and normalization equation between native-coins of VMs. |
||
| } | ||
|
|
||
| function testNativeToErc20Supra() public { | ||
| vm.prank(alice); | ||
| token.nativeToErc20Supra{value: 5 ether}(); | ||
|
|
||
| assertEq(token.balanceOf(alice), 5 ether); | ||
| assertEq(address(token).balance, 5 ether); | ||
| assertEq(address(token).balance, token.totalSupply()); | ||
| assertEq(alice.balance, 95 ether); | ||
| } | ||
|
|
||
| function testNativeToErc20SupraRevertsIfAmountZero() public { | ||
| vm.expectRevert(ERC20Supra.InvalidAmount.selector); | ||
|
|
||
| vm.prank(alice); | ||
| token.nativeToErc20Supra{value: 0}(); | ||
| } | ||
|
|
||
| function testReceiveMintsERC20Supra() public { | ||
| vm.prank(alice); | ||
| (bool success, ) = address(token).call{value: 3 ether}(""); | ||
| require(success); | ||
|
|
||
| assertEq(token.balanceOf(alice), 3 ether); | ||
| assertEq(address(token).balance, 3 ether); | ||
| assertEq(alice.balance, 97 ether); | ||
| } | ||
|
|
||
| function testReceiveRevertsIfAmountZero() public { | ||
| vm.expectRevert(ERC20Supra.InvalidAmount.selector); | ||
|
|
||
| vm.prank(alice); | ||
| address(token).call{value: 0}(""); | ||
| } | ||
|
|
||
| function testErc20SupraToNative() public { | ||
| // Alice deposits 5 SUPRA → gets 5 * 10 ** 18 ERC20Supra tokens | ||
| testNativeToErc20Supra(); | ||
|
|
||
| // Alice withdraws 3 SUPRA → burns 3 * 10 ** 18 ERC20Supra tokens | ||
| vm.prank(alice); | ||
| token.erc20SupraToNative(3 ether); | ||
|
|
||
| assertEq(token.balanceOf(alice), 2 ether); | ||
| assertEq(address(alice).balance, 98 ether); | ||
| assertEq(address(token).balance, 2 ether); | ||
| assertEq(address(token).balance, token.totalSupply()); | ||
| } | ||
|
|
||
| function testErc20SupraToNativeRevertsIfInsufficientBalance() public { | ||
| vm.expectRevert(ERC20Supra.InsufficientBalance.selector); | ||
|
|
||
| vm.prank(alice); | ||
| token.erc20SupraToNative(1 ether); | ||
| } | ||
|
|
||
| function testErc20SupraToNativeRevertsIfAmountZero() public { | ||
| vm.expectRevert(ERC20Supra.InvalidAmount.selector); | ||
|
|
||
| vm.prank(alice); | ||
| token.erc20SupraToNative(0); | ||
| } | ||
|
|
||
| function testErc20SupraToNativeRevertsIfNativeTransferFails() public { | ||
| // Mint tokens | ||
| vm.prank(alice); | ||
| token.nativeToErc20Supra{value: 1 ether}(); | ||
|
|
||
| RejectReceive rejector = new RejectReceive(); | ||
|
|
||
| // Transfer tokens to the rejecting contract | ||
| vm.prank(alice); | ||
| token.transfer(address(rejector), 1 ether); | ||
|
|
||
| // Attempt withdrawal → should revert | ||
| vm.expectRevert(ERC20Supra.TransferFailed.selector); | ||
|
|
||
| vm.prank(address(rejector)); | ||
| token.erc20SupraToNative(1 ether); | ||
|
|
||
| assertEq(token.balanceOf(address(rejector)), 1 ether); | ||
| } | ||
|
|
||
| function testCannotTransferToContract() public { | ||
| vm.prank(alice); | ||
| token.nativeToErc20Supra{value: 1 ether}(); | ||
|
|
||
| vm.expectRevert(ERC20Supra.InvalidTransfer.selector); | ||
|
|
||
| vm.prank(alice); | ||
| token.transfer(address(token), 1 ether); | ||
| } | ||
|
|
||
| function testMintToContractReverts() public { | ||
| vm.deal(address(token), 1 ether); | ||
|
|
||
| vm.expectRevert(ERC20Supra.InvalidTransfer.selector); | ||
|
|
||
| vm.prank(address(token)); | ||
| token.nativeToErc20Supra{value: 1 ether}(); | ||
| } | ||
|
|
||
| // Additional test cases for ERC20Supra | ||
| function testTransferBetweenUsers() public { | ||
| vm.prank(alice); | ||
| token.nativeToErc20Supra{value: 5 ether}(); | ||
|
|
||
| assertEq(token.balanceOf(alice) , 5 ether); | ||
|
|
||
| vm.prank(alice); | ||
| token.transfer(bob, 2 ether); | ||
|
|
||
| assertEq(token.balanceOf(alice), 3 ether); | ||
| assertEq(token.balanceOf(bob), 2 ether); | ||
| } | ||
|
|
||
| function testTransferFromAllowance() public { | ||
| vm.prank(alice); | ||
| token.nativeToErc20Supra{value: 5 ether}(); | ||
|
|
||
| vm.prank(alice); | ||
| token.approve(bob, 3 ether); | ||
|
|
||
| vm.prank(bob); | ||
| token.transferFrom(alice, bob, 2 ether); | ||
|
|
||
| assertEq(token.balanceOf(alice), 3 ether); | ||
| assertEq(token.balanceOf(bob), 2 ether); | ||
| assertEq(token.allowance(alice, bob), 1 ether); | ||
| } | ||
|
|
||
| function testBurnFromReducesBalance() public { | ||
| vm.prank(alice); | ||
| token.nativeToErc20Supra{value: 5 ether}(); | ||
|
|
||
| vm.prank(alice); | ||
| token.approve(bob, 3 ether); | ||
|
|
||
| vm.prank(bob); | ||
| token.burnFrom(alice, 2 ether); | ||
|
|
||
| assertEq(token.balanceOf(alice), 3 ether); | ||
| assertEq(token.allowance(alice, bob), 1 ether); | ||
| assertEq(token.totalSupply(), 3 ether); | ||
| } | ||
|
|
||
| function testTotalSupplyEqualsContractBalance() public { | ||
| vm.prank(alice); | ||
| token.nativeToErc20Supra{value: 3 ether}(); | ||
| vm.prank(bob); | ||
| token.nativeToErc20Supra{value: 2 ether}(); | ||
|
|
||
| vm.prank(alice); | ||
| token.erc20SupraToNative(1 ether); | ||
| vm.prank(bob); | ||
| token.erc20SupraToNative(2 ether); | ||
|
|
||
| assertEq(address(token).balance, token.totalSupply()); | ||
| assertEq(token.totalSupply(), 2 ether); | ||
| assertEq(token.balanceOf(alice), 2 ether); | ||
| assertEq(token.balanceOf(bob), 0); | ||
| } | ||
|
|
||
| function testNativeToErc20SupraEmitsEvent() public { | ||
| vm.expectEmit(true, true, false, false); | ||
| emit ERC20Supra.NativeToERC20Supra(alice, 5 ether); | ||
|
|
||
| vm.prank(alice); | ||
| token.nativeToErc20Supra{value: 5 ether}(); | ||
| } | ||
|
|
||
| function testReceiveEmitsEvent() public { | ||
| vm.expectEmit(true, true, false, false); | ||
| emit ERC20Supra.NativeToERC20Supra(alice, 3 ether); | ||
|
|
||
| vm.prank(alice); | ||
| (bool success, ) = address(token).call{value: 3 ether}(""); | ||
| require(success); | ||
| } | ||
|
|
||
| function testErc20SupraToNativeEmitsEvent() public { | ||
| vm.prank(alice); | ||
| token.nativeToErc20Supra{value: 5 ether}(); | ||
|
|
||
| vm.expectEmit(true, true, false, false); | ||
| emit ERC20Supra.ERC20SupraToNative(alice, 2 ether); | ||
|
|
||
| vm.prank(alice); | ||
| token.erc20SupraToNative(2 ether); | ||
| } | ||
| } | ||
|
|
||
| contract RejectReceive { | ||
| fallback() external payable { revert(); } | ||
| receive() external payable { revert(); } | ||
| } | ||
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.
@udityadav-supraoracles I do not see method to delegate ERC20 to other address etc, are those functionalities handled in Parent contract?
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, if a user wants to delegate the authority to spend their ERC20 they can call
approvewhich is defined in parentERC20.