Chapter 3
ERC-20 Token Standardhttps://eips.ethereum.org/EIPS/eip-20
This standard provides basic functionality to transfer tokens, as well as allow tokens to be approved so they can be spent by another on-chain third party.
Delegate Transfer
Its a two step process
One function is going to allow our account to approve the transfer
Another function is going to handle delegated transfer
approve
Allows
_spender
to withdraw from your account multiple times, up to the _value
amount. If this function is called again it overwrites the current allowance with _value
.NOTE: To prevent attack vectors like the one described here and discussed here, clients SHOULD make sure to create user interfaces in such a way that they set the allowance first to
0
before setting it to another value for the same spender. THOUGH The contract itself shouldn’t enforce it, to allow backwards compatibility with contracts deployed beforefunction approve(address _spender, uint256 _value) public returns (bool success)
transferFrom
Transfers
_value
amount of tokens from address _from
to address _to
, and MUST fire the Transfer
event.The
transferFrom
method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf. This can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in sub-currencies. The function SHOULD throw
unless the _from
account has deliberately authorized the sender of the message via some mechanism.Note Transfers of 0 values MUST be treated as normal transfers and fire the
Transfer
event.function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
allowance
Returns the amount which
_spender
is still allowed to withdraw from _owner
.function allowance(address _owner, address _spender) public view returns (uint256 remaining)
Approval
MUST trigger on any successful call to
approve(address _spender, uint256 _value)
.event Approval(address indexed _owner, address indexed _spender, uint256 _value)
Now lets code...
DappToken.sol
function approve(address _spender, uint256 _value) public returns (bool success) {
return true;
}
return true;
}
Remember from last chapter:
approve.call(accounts[1],100)
does not create a transaction and don't write to blockchain.
DappToken.js test
it('approves tokens for delegated transfer', function(){
return DappToken.deployed().then(function(instance) {
tokenInstance = instance;
return tokenInstance.approve.call(accounts[1], 100);
}).then(function(success){
assert.equal(success, true, 'it returns true')
});
})
return DappToken.deployed().then(function(instance) {
tokenInstance = instance;
return tokenInstance.approve.call(accounts[1], 100);
}).then(function(success){
assert.equal(success, true, 'it returns true')
});
})
Now call approve without call to trigger event:
it('approves tokens for delegated transfer', function(){
return DappToken.deployed().then(function(instance) {
tokenInstance = instance;
return tokenInstance.approve.call(accounts[1], 100);
}).then(function(success){
assert.equal(success, true, 'it returns true')
return tokenInstance.approve(accounts[1], 100)
}).then(function(receipt){
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Approval", 'should be "Approval" event');
assert.equal(receipt.logs[0].args._owner, accounts[0], "logs the account the tokens are authorized by");
assert.equal(receipt.logs[0].args._spender, accounts[1], "logs the account the tokens are authorized to");
assert.equal(receipt.logs[0].args._value, 100, "logs the transfer amount");
});
})
return DappToken.deployed().then(function(instance) {
tokenInstance = instance;
return tokenInstance.approve.call(accounts[1], 100);
}).then(function(success){
assert.equal(success, true, 'it returns true')
return tokenInstance.approve(accounts[1], 100)
}).then(function(receipt){
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Approval", 'should be "Approval" event');
assert.equal(receipt.logs[0].args._owner, accounts[0], "logs the account the tokens are authorized by");
assert.equal(receipt.logs[0].args._spender, accounts[1], "logs the account the tokens are authorized to");
assert.equal(receipt.logs[0].args._value, 100, "logs the transfer amount");
});
})
DappToken.sol
// 2. Approval even, see the documentation
// I the owner, approved _spender to spend _value number of dapp tokens
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
// I the owner, approved _spender to spend _value number of dapp tokens
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
// 1. Approve function (Allows _spender to withdraw from your account multiple times)
function approve(address _spender, uint256 _value) public returns (bool success) {
// 3. Call the Approval event
emit Approval(msg.sender, _spender, _value);
return true;
}
function approve(address _spender, uint256 _value) public returns (bool success) {
// 3. Call the Approval event
emit Approval(msg.sender, _spender, _value);
return true;
}
Now lets set the allowance, (hay this account is allowed to spend this much from my account)
mapping(address => mapping(address => uint256)) public allowance;
allownance variable, it contains nested mapping. We have mapping within a mapping
First address is owner account, second addresses are of other users who are allowed to spend on behalf of owner, and uint256 is the value.
Add from from address
return tokenInstance.approve(accounts[1], 100, {from: accounts[0]})
Then test for allowance
assert.equal(allowance, 100, 'stores the allowance for delegated transfer');
it('approves tokens for delegated transfer', function(){
return DappToken.deployed().then(function(instance) {
tokenInstance = instance;
return tokenInstance.approve.call(accounts[1], 100);
}).then(function(success){
assert.equal(success, true, 'it returns true')
return tokenInstance.approve(accounts[1], 100, {from: accounts[0]})
}).then(function(receipt){
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Approval", 'should be "Approval" event');
assert.equal(receipt.logs[0].args._owner, accounts[0], "logs the account the tokens are authorized by");
assert.equal(receipt.logs[0].args._spender, accounts[1], "logs the account the tokens are authorized to");
assert.equal(receipt.logs[0].args._value, 100, "logs the transfer amount");
}).then(function(allowance) {
assert.equal(allowance, 100, 'stores the allowance for delegated transfer');
});
})
return DappToken.deployed().then(function(instance) {
tokenInstance = instance;
return tokenInstance.approve.call(accounts[1], 100);
}).then(function(success){
assert.equal(success, true, 'it returns true')
return tokenInstance.approve(accounts[1], 100, {from: accounts[0]})
}).then(function(receipt){
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Approval", 'should be "Approval" event');
assert.equal(receipt.logs[0].args._owner, accounts[0], "logs the account the tokens are authorized by");
assert.equal(receipt.logs[0].args._spender, accounts[1], "logs the account the tokens are authorized to");
assert.equal(receipt.logs[0].args._value, 100, "logs the transfer amount");
}).then(function(allowance) {
assert.equal(allowance, 100, 'stores the allowance for delegated transfer');
});
})
pragma solidity >=0.4.22 <0.7.0;
contract DappToken {
string public name = "DApp Token";
string public symbol = "DAPP";
string public standard = "DApp Token v1.0";
uint256 public totalSupply;
event Transfer(
address indexed _from,
address indexed _to,
uint256 _value
);
// 2. Approval even, see the documentation
// I the owner, approved _spender to spend _value number of dapp tokens
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
mapping(address => uint256) public balanceOf;
// 4. allownance variable, it contains nested mapping. We have mapping within a mapping
mapping(address => mapping(address => uint256)) public allowance;
constructor (uint256 _initialSupply) public {
balanceOf[msg.sender] = _initialSupply;
totalSupply = _initialSupply;
}
function transfer(address _to, uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value, "Don't have enough money");
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
// 1. Approve function (Allows _spender to withdraw from your account multiple times)
function approve(address _spender, uint256 _value) public returns (bool success) {
// 5. Allowance
allowance[msg.sender][_spender] = _value;
// 3. Call the Approval event
emit Approval(msg.sender, _spender, _value);
return true;
}
}
contract DappToken {
string public name = "DApp Token";
string public symbol = "DAPP";
string public standard = "DApp Token v1.0";
uint256 public totalSupply;
event Transfer(
address indexed _from,
address indexed _to,
uint256 _value
);
// 2. Approval even, see the documentation
// I the owner, approved _spender to spend _value number of dapp tokens
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
mapping(address => uint256) public balanceOf;
// 4. allownance variable, it contains nested mapping. We have mapping within a mapping
mapping(address => mapping(address => uint256)) public allowance;
constructor (uint256 _initialSupply) public {
balanceOf[msg.sender] = _initialSupply;
totalSupply = _initialSupply;
}
function transfer(address _to, uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value, "Don't have enough money");
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
// 1. Approve function (Allows _spender to withdraw from your account multiple times)
function approve(address _spender, uint256 _value) public returns (bool success) {
// 5. Allowance
allowance[msg.sender][_spender] = _value;
// 3. Call the Approval event
emit Approval(msg.sender, _spender, _value);
return true;
}
}