mirror of
https://github.com/status-im/nim-ethers.git
synced 2025-01-13 00:54:38 +00:00
feat: erc20 module (#38)
Co-authored-by: Eric Mastro <github@egonat.me>
This commit is contained in:
parent
577e02b8a2
commit
3c12a65769
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
|||||||
!*.*
|
!*.*
|
||||||
nimble.develop
|
nimble.develop
|
||||||
nimble.paths
|
nimble.paths
|
||||||
|
.idea
|
||||||
|
19
Readme.md
19
Readme.md
@ -143,6 +143,25 @@ await subscription.unsubscribe()
|
|||||||
Subscriptions are currently only supported when using a JSON RPC provider that
|
Subscriptions are currently only supported when using a JSON RPC provider that
|
||||||
is created with a websockets URL such as `ws://localhost:8545`.
|
is created with a websockets URL such as `ws://localhost:8545`.
|
||||||
|
|
||||||
|
Utilities
|
||||||
|
---------
|
||||||
|
|
||||||
|
This library ships with some optional modules that provides convenience utilities for you such as:
|
||||||
|
|
||||||
|
- `ethers/erc20` module provides you with ERC20 token implementation and its events
|
||||||
|
|
||||||
|
Contribution
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you want to run the tests, then before running `nimble test`, you have to
|
||||||
|
have installed NodeJS and started a testing node:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cd testnode
|
||||||
|
$ npm ci
|
||||||
|
$ npm start
|
||||||
|
```
|
||||||
|
|
||||||
Thanks
|
Thanks
|
||||||
------
|
------
|
||||||
|
|
||||||
|
52
ethers/erc20.nim
Normal file
52
ethers/erc20.nim
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import pkg/stint
|
||||||
|
import pkg/ethers
|
||||||
|
|
||||||
|
export stint
|
||||||
|
export ethers
|
||||||
|
|
||||||
|
type
|
||||||
|
Erc20Token* = ref object of Contract
|
||||||
|
|
||||||
|
Transfer* = object of Event
|
||||||
|
sender* {.indexed.}: Address
|
||||||
|
receiver* {.indexed.}: Address
|
||||||
|
value*: UInt256
|
||||||
|
|
||||||
|
Approval* = object of Event
|
||||||
|
owner* {.indexed.}: Address
|
||||||
|
spender* {.indexed.}: Address
|
||||||
|
value*: UInt256
|
||||||
|
|
||||||
|
method name*(erc20: Erc20Token): string {.base, contract, view.}
|
||||||
|
## Returns the name of the token.
|
||||||
|
|
||||||
|
method symbol*(token: Erc20Token): string {.base, contract, view.}
|
||||||
|
## Returns the symbol of the token, usually a shorter version of the name.
|
||||||
|
|
||||||
|
method decimals*(token: Erc20Token): uint8 {.base, contract, view.}
|
||||||
|
## Returns the number of decimals used to get its user representation.
|
||||||
|
## For example, if `decimals` equals `2`, a balance of `505` tokens should
|
||||||
|
## be displayed to a user as `5.05` (`505 / 10 ** 2`).
|
||||||
|
|
||||||
|
method totalSupply*(erc20: Erc20Token): UInt256 {.base, contract, view.}
|
||||||
|
## Returns the amount of tokens in existence.
|
||||||
|
|
||||||
|
method balanceOf*(erc20: Erc20Token, account: Address): UInt256 {.base, contract, view.}
|
||||||
|
## Returns the amount of tokens owned by `account`.
|
||||||
|
|
||||||
|
method allowance*(erc20: Erc20Token, owner, spender: Address): UInt256 {.base, contract, view.}
|
||||||
|
## Returns the remaining number of tokens that `spender` will be allowed
|
||||||
|
## to spend on behalf of `owner` through {transferFrom}. This is zero by default.
|
||||||
|
##
|
||||||
|
## This value changes when {approve} or {transferFrom} are called.
|
||||||
|
|
||||||
|
method transfer*(erc20: Erc20Token, recipient: Address, amount: UInt256) {.base, contract.}
|
||||||
|
## Moves `amount` tokens from the caller's account to `recipient`.
|
||||||
|
|
||||||
|
method approve*(token: Erc20Token, spender: Address, amount: UInt256) {.base, contract.}
|
||||||
|
## Sets `amount` as the allowance of `spender` over the caller's tokens.
|
||||||
|
|
||||||
|
method transferFrom*(erc20: Erc20Token, spender: Address, recipient: Address, amount: UInt256) {.base, contract.}
|
||||||
|
## Moves `amount` tokens from `from` to `to` using the allowance
|
||||||
|
## mechanism. `amount` is then deducted from the caller's allowance.
|
||||||
|
|
@ -6,5 +6,6 @@ import ./testEnums
|
|||||||
import ./testEvents
|
import ./testEvents
|
||||||
import ./testWallet
|
import ./testWallet
|
||||||
import ./testTesting
|
import ./testTesting
|
||||||
|
import ./testErc20
|
||||||
|
|
||||||
{.warning[UnusedImport]:off.}
|
{.warning[UnusedImport]:off.}
|
||||||
|
@ -3,25 +3,15 @@ import pkg/asynctest
|
|||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
import pkg/stint
|
import pkg/stint
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
|
import pkg/ethers/erc20
|
||||||
import ./hardhat
|
import ./hardhat
|
||||||
import ./miner
|
import ./miner
|
||||||
import ./mocks
|
import ./mocks
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
||||||
Erc20* = ref object of Contract
|
TestToken = ref object of Erc20Token
|
||||||
TestToken = ref object of Erc20
|
|
||||||
|
|
||||||
Transfer = object of Event
|
|
||||||
sender {.indexed.}: Address
|
|
||||||
receiver {.indexed.}: Address
|
|
||||||
value: UInt256
|
|
||||||
|
|
||||||
method name*(erc20: Erc20): string {.base, contract, view.}
|
|
||||||
method totalSupply*(erc20: Erc20): UInt256 {.base, contract, view.}
|
|
||||||
method balanceOf*(erc20: Erc20, account: Address): UInt256 {.base, contract, view.}
|
|
||||||
method allowance*(erc20: Erc20, owner, spender: Address): UInt256 {.base, contract, view.}
|
|
||||||
method transfer*(erc20: Erc20, recipient: Address, amount: UInt256) {.base, contract.}
|
|
||||||
method mint(token: TestToken, holder: Address, amount: UInt256): ?TransactionResponse {.base, contract.}
|
method mint(token: TestToken, holder: Address, amount: UInt256): ?TransactionResponse {.base, contract.}
|
||||||
|
|
||||||
suite "Contracts":
|
suite "Contracts":
|
||||||
|
96
testmodule/testErc20.nim
Normal file
96
testmodule/testErc20.nim
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import std/json
|
||||||
|
import pkg/asynctest
|
||||||
|
import pkg/questionable
|
||||||
|
import pkg/stint
|
||||||
|
import pkg/ethers
|
||||||
|
import pkg/ethers/erc20
|
||||||
|
import ./hardhat
|
||||||
|
import ./miner
|
||||||
|
import ./mocks
|
||||||
|
|
||||||
|
type
|
||||||
|
TestToken = ref object of Erc20Token
|
||||||
|
|
||||||
|
method mint(token: TestToken, holder: Address, amount: UInt256): ?TransactionResponse {.base, contract.}
|
||||||
|
|
||||||
|
suite "ERC20":
|
||||||
|
|
||||||
|
var token, token1: Erc20Token
|
||||||
|
var testToken: TestToken
|
||||||
|
var provider: JsonRpcProvider
|
||||||
|
var snapshot: JsonNode
|
||||||
|
var accounts: seq[Address]
|
||||||
|
|
||||||
|
setup:
|
||||||
|
provider = JsonRpcProvider.new("ws://localhost:8545")
|
||||||
|
snapshot = await provider.send("evm_snapshot")
|
||||||
|
accounts = await provider.listAccounts()
|
||||||
|
let deployment = readDeployment()
|
||||||
|
testToken = TestToken.new(!deployment.address(TestToken), provider.getSigner())
|
||||||
|
token = Erc20Token.new(!deployment.address(TestToken), provider.getSigner())
|
||||||
|
|
||||||
|
teardown:
|
||||||
|
discard await provider.send("evm_revert", @[snapshot])
|
||||||
|
|
||||||
|
test "retrieves basic information":
|
||||||
|
check (await token.name()) == "TestToken"
|
||||||
|
check (await token.symbol()) == "TST"
|
||||||
|
check (await token.decimals()) == 12
|
||||||
|
check (await token.totalSupply()) == 0.u256
|
||||||
|
check (await token.balanceOf(accounts[0])) == 0.u256
|
||||||
|
check (await token.allowance(accounts[0], accounts[1])) == 0.u256
|
||||||
|
|
||||||
|
test "transfer tokens":
|
||||||
|
check (await token.balanceOf(accounts[0])) == 0.u256
|
||||||
|
check (await token.allowance(accounts[0], accounts[1])) == 0.u256
|
||||||
|
|
||||||
|
discard await testToken.mint(accounts[0], 100.u256)
|
||||||
|
|
||||||
|
check (await token.totalSupply()) == 100.u256
|
||||||
|
check (await token.balanceOf(accounts[0])) == 100.u256
|
||||||
|
check (await token.balanceOf(accounts[1])) == 0.u256
|
||||||
|
|
||||||
|
await token.transfer(accounts[1], 50.u256)
|
||||||
|
|
||||||
|
check (await token.balanceOf(accounts[0])) == 50.u256
|
||||||
|
check (await token.balanceOf(accounts[1])) == 50.u256
|
||||||
|
|
||||||
|
test "approve tokens":
|
||||||
|
discard await testToken.mint(accounts[0], 100.u256)
|
||||||
|
|
||||||
|
check (await token.allowance(accounts[0], accounts[1])) == 0.u256
|
||||||
|
check (await token.balanceOf(accounts[0])) == 100.u256
|
||||||
|
check (await token.balanceOf(accounts[1])) == 0.u256
|
||||||
|
|
||||||
|
await token.approve(accounts[1], 50.u256)
|
||||||
|
|
||||||
|
check (await token.allowance(accounts[0], accounts[1])) == 50.u256
|
||||||
|
check (await token.balanceOf(accounts[0])) == 100.u256
|
||||||
|
check (await token.balanceOf(accounts[1])) == 0.u256
|
||||||
|
|
||||||
|
test "transferFrom tokens":
|
||||||
|
let senderAccount = accounts[0]
|
||||||
|
let receiverAccount = accounts[1]
|
||||||
|
let receiverAccountSigner = provider.getSigner(receiverAccount)
|
||||||
|
|
||||||
|
check (await token.balanceOf(senderAccount)) == 0.u256
|
||||||
|
check (await token.allowance(senderAccount, receiverAccount)) == 0.u256
|
||||||
|
|
||||||
|
discard await testToken.mint(senderAccount, 100.u256)
|
||||||
|
|
||||||
|
check (await token.totalSupply()) == 100.u256
|
||||||
|
check (await token.balanceOf(senderAccount)) == 100.u256
|
||||||
|
check (await token.balanceOf(receiverAccount)) == 0.u256
|
||||||
|
|
||||||
|
await token.approve(receiverAccount, 50.u256)
|
||||||
|
|
||||||
|
check (await token.allowance(senderAccount, receiverAccount)) == 50.u256
|
||||||
|
check (await token.balanceOf(senderAccount)) == 100.u256
|
||||||
|
check (await token.balanceOf(receiverAccount)) == 0.u256
|
||||||
|
|
||||||
|
await token.connect(receiverAccountSigner).transferFrom(senderAccount, receiverAccount, 50.u256)
|
||||||
|
|
||||||
|
check (await token.balanceOf(senderAccount)) == 50.u256
|
||||||
|
check (await token.balanceOf(receiverAccount)) == 50.u256
|
||||||
|
check (await token.allowance(senderAccount, receiverAccount)) == 0.u256
|
||||||
|
|
@ -6,7 +6,15 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|||||||
contract TestToken is ERC20 {
|
contract TestToken is ERC20 {
|
||||||
constructor() ERC20("TestToken", "TST") {}
|
constructor() ERC20("TestToken", "TST") {}
|
||||||
|
|
||||||
|
function decimals() public view virtual override returns (uint8) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
|
||||||
function mint(address holder, uint amount) public {
|
function mint(address holder, uint amount) public {
|
||||||
_mint(holder, amount);
|
_mint(holder, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function burn(address holder, uint amount) public {
|
||||||
|
_burn(holder, amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user