Chapter 4
transferFromAfter approval, transfer tokens on behalf.
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;
}
// 6. Transfer money, similar to transfer function, but its done by me (msg.sender) on behalf of _from address
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
// 7. Check if _from account has enough balance
require(_value <= balanceOf[_from], "You are trying to spend more than user has");
// 8. Check if msg.sender tranfers amount of money that is allowed
require(_value <= allowance[_from][msg.sender], "You are trying to spend more than allowed limit");
// 9. Tranfer event
emit Transfer(_from, _to, _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;
}
// 6. Transfer money, similar to transfer function, but its done by me (msg.sender) on behalf of _from address
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
// 7. Check if _from account has enough balance
require(_value <= balanceOf[_from], "You are trying to spend more than user has");
// 8. Check if msg.sender tranfers amount of money that is allowed
require(_value <= allowance[_from][msg.sender], "You are trying to spend more than allowed limit");
// 9. Tranfer event
emit Transfer(_from, _to, _value);
return true;
}
}
Tests:
it("handles delegated token transfers", function() {
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
fromAccount = accounts[2];
toAccount = accounts[3];
spendingAccount = accounts[4]; // This is msg.sender
// Tranfer some tokens to fromAccount, so that it has some tokens to transfer
return tokenInstance.transfer(fromAccount, 100, { from: accounts[0] });
})
.then(function(receipt) {
// Approve spendingAccount to spend 10 tokens from fromAccount
return tokenInstance.approve(spendingAccount, 10, { from: fromAccount });
})
.then(function(receipt) {
// Try transferring something larger than the sender's balance
return tokenInstance.transferFrom(fromAccount, toAccount, 9999, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than balance");
// Try transferring something larger than the approved amount
return tokenInstance.transferFrom(fromAccount, toAccount, 20, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than approved amount");
return tokenInstance.transferFrom.call(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(success){
assert.equal(success, true);
return tokenInstance.transferFrom(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(receipt){
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Transfer", 'should be "Transfer" event');
assert.equal(receipt.logs[0].args._from, fromAccount, "logs the account the tokens are transferred from");
assert.equal(receipt.logs[0].args._to, toAccount, "logs the account the tokens are transferred to");
assert.equal(receipt.logs[0].args._value, 10, "logs the transfer amount");
});
});
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
fromAccount = accounts[2];
toAccount = accounts[3];
spendingAccount = accounts[4]; // This is msg.sender
// Tranfer some tokens to fromAccount, so that it has some tokens to transfer
return tokenInstance.transfer(fromAccount, 100, { from: accounts[0] });
})
.then(function(receipt) {
// Approve spendingAccount to spend 10 tokens from fromAccount
return tokenInstance.approve(spendingAccount, 10, { from: fromAccount });
})
.then(function(receipt) {
// Try transferring something larger than the sender's balance
return tokenInstance.transferFrom(fromAccount, toAccount, 9999, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than balance");
// Try transferring something larger than the approved amount
return tokenInstance.transferFrom(fromAccount, toAccount, 20, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than approved amount");
return tokenInstance.transferFrom.call(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(success){
assert.equal(success, true);
return tokenInstance.transferFrom(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(receipt){
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Transfer", 'should be "Transfer" event');
assert.equal(receipt.logs[0].args._from, fromAccount, "logs the account the tokens are transferred from");
assert.equal(receipt.logs[0].args._to, toAccount, "logs the account the tokens are transferred to");
assert.equal(receipt.logs[0].args._value, 10, "logs the transfer amount");
});
});
Deduct balance and deduct allowance amount:
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;
}
// 6. Transfer money, similar to transfer function, but its done by me (msg.sender) on behalf of _from address
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
// 7. Check if _from account has enough balance
require(_value <= balanceOf[_from], "You are trying to spend more than user has");
// 8. Check if msg.sender tranfers amount of money that is allowed
require(_value <= allowance[_from][msg.sender], "You are trying to spend more than allowed limit");
// 10. Deduct balance _from account, and adding it to _to account
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
// 11. Deduct the allowance
allowance[_from][msg.sender] -= _value;
// 9. Tranfer event
emit Transfer(_from, _to, _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;
}
// 6. Transfer money, similar to transfer function, but its done by me (msg.sender) on behalf of _from address
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
// 7. Check if _from account has enough balance
require(_value <= balanceOf[_from], "You are trying to spend more than user has");
// 8. Check if msg.sender tranfers amount of money that is allowed
require(_value <= allowance[_from][msg.sender], "You are trying to spend more than allowed limit");
// 10. Deduct balance _from account, and adding it to _to account
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
// 11. Deduct the allowance
allowance[_from][msg.sender] -= _value;
// 9. Tranfer event
emit Transfer(_from, _to, _value);
return true;
}
}
Tests:
it("handles delegated token transfers", function() {
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
fromAccount = accounts[2];
toAccount = accounts[3];
spendingAccount = accounts[4]; // This is msg.sender
// Tranfer some tokens to fromAccount, so that it has some tokens to transfer
return tokenInstance.transfer(fromAccount, 100, { from: accounts[0] });
})
.then(function(receipt) {
// Approve spendingAccount to spend 10 tokens from fromAccount
return tokenInstance.approve(spendingAccount, 10, { from: fromAccount });
})
.then(function(receipt) {
// Try transferring something larger than the sender's balance
return tokenInstance.transferFrom(fromAccount, toAccount, 9999, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than balance");
// Try transferring something larger than the approved amount
return tokenInstance.transferFrom(fromAccount, toAccount, 20, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than approved amount");
return tokenInstance.transferFrom.call(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(success){
assert.equal(success, true);
return tokenInstance.transferFrom(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(receipt){
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Transfer", 'should be "Transfer" event');
assert.equal(receipt.logs[0].args._from, fromAccount, "logs the account the tokens are transferred from");
assert.equal(receipt.logs[0].args._to, toAccount, "logs the account the tokens are transferred to");
assert.equal(receipt.logs[0].args._value, 10, "logs the transfer amount");
return tokenInstance.balanceOf(fromAccount);
}).then(function(balance){
assert.equal(balance.toNumber(), 90, 'left amount after deduction');
return tokenInstance.balanceOf(toAccount);
}).then(function(balance){
assert.equal(balance.toNumber(), 10, 'adds the amount from the recieving account');
return tokenInstance.allowance(fromAccount, spendingAccount);
}).then(function(allowance) {
assert.equal(allowance.toNumber(), 0, 'deducts the amount from the allowance');
})
});
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
fromAccount = accounts[2];
toAccount = accounts[3];
spendingAccount = accounts[4]; // This is msg.sender
// Tranfer some tokens to fromAccount, so that it has some tokens to transfer
return tokenInstance.transfer(fromAccount, 100, { from: accounts[0] });
})
.then(function(receipt) {
// Approve spendingAccount to spend 10 tokens from fromAccount
return tokenInstance.approve(spendingAccount, 10, { from: fromAccount });
})
.then(function(receipt) {
// Try transferring something larger than the sender's balance
return tokenInstance.transferFrom(fromAccount, toAccount, 9999, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than balance");
// Try transferring something larger than the approved amount
return tokenInstance.transferFrom(fromAccount, toAccount, 20, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than approved amount");
return tokenInstance.transferFrom.call(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(success){
assert.equal(success, true);
return tokenInstance.transferFrom(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(receipt){
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Transfer", 'should be "Transfer" event');
assert.equal(receipt.logs[0].args._from, fromAccount, "logs the account the tokens are transferred from");
assert.equal(receipt.logs[0].args._to, toAccount, "logs the account the tokens are transferred to");
assert.equal(receipt.logs[0].args._value, 10, "logs the transfer amount");
return tokenInstance.balanceOf(fromAccount);
}).then(function(balance){
assert.equal(balance.toNumber(), 90, 'left amount after deduction');
return tokenInstance.balanceOf(toAccount);
}).then(function(balance){
assert.equal(balance.toNumber(), 10, 'adds the amount from the recieving account');
return tokenInstance.allowance(fromAccount, spendingAccount);
}).then(function(allowance) {
assert.equal(allowance.toNumber(), 0, 'deducts the amount from the allowance');
})
});
Full test code (DappToken.js):
const DappToken = artifacts.require("DappToken");
contract("DappToken", function(accounts) {
var tokenInstance;
it("initialize the contract with correct values", function() {
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
return tokenInstance.name();
})
.then(function(name) {
assert.equal(name, "DApp Token", "has the correct name");
return tokenInstance.symbol();
})
.then(function(symbol) {
assert.equal(symbol, "DAPP", "has the correct symbol");
return tokenInstance.standard();
})
.then(function(standard) {
assert.equal(standard, "DApp Token v1.0", "has the correct standard");
});
});
it("allocates the initial supply supply upon deployment", function() {
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
return tokenInstance.totalSupply();
})
.then(function(totalSupply) {
assert.equal(totalSupply.toNumber(), 1000000, "sets the total supply to 1,000,000");
return tokenInstance.balanceOf(accounts[0]);
})
.then(function(adminBalance) {
assert.equal(adminBalance.toNumber(), 1000000, "it allocates the intial supply to the admin account");
});
});
it("transfer token ownership", function() {
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
// console.log(accounts[1])
return tokenInstance.transfer.call(accounts[1], 999999999999);
})
.then(assert.fail)
.catch(function(error) {
// console.log(error.message)
assert(error.message.indexOf("revert") >= 0, "error message must contain revert");
return tokenInstance.transfer.call(accounts[1], 250000, { from: accounts[0] });
})
.then(function(success) {
assert.equal(success, true, "it return true");
return tokenInstance.transfer(accounts[1], 250000, { from: accounts[0] });
})
.then(function(receipt) {
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Transfer", 'should be "Transfer event');
assert.equal(receipt.logs[0].args._from, accounts[0], "logs the account the tokens are transferred from");
assert.equal(receipt.logs[0].args._to, accounts[1], "logs the account the tokens are transferred to");
assert.equal(receipt.logs[0].args._value, 250000, "logs the transfer amount");
return tokenInstance.balanceOf(accounts[1]);
})
.then(function(balance) {
assert.equal(balance.toNumber(), 250000, "adds the amount to the receiving account");
return tokenInstance.balanceOf(accounts[0]);
})
.then(function(balance) {
assert.equal(balance.toNumber(), 750000, "deducts the amount from the sending account");
});
});
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");
return tokenInstance.allowance(accounts[0], accounts[1]);
})
.then(function(allowance) {
assert.equal(allowance.toNumber(), 100, "stores the allowance for delegated transfer");
});
});
it("handles delegated token transfers", function() {
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
fromAccount = accounts[2];
toAccount = accounts[3];
spendingAccount = accounts[4]; // This is msg.sender
// Tranfer some tokens to fromAccount, so that it has some tokens to transfer
return tokenInstance.transfer(fromAccount, 100, { from: accounts[0] });
})
.then(function(receipt) {
// Approve spendingAccount to spend 10 tokens from fromAccount
return tokenInstance.approve(spendingAccount, 10, { from: fromAccount });
})
.then(function(receipt) {
// Try transferring something larger than the sender's balance
return tokenInstance.transferFrom(fromAccount, toAccount, 9999, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than balance");
// Try transferring something larger than the approved amount
return tokenInstance.transferFrom(fromAccount, toAccount, 20, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than approved amount");
return tokenInstance.transferFrom.call(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(success){
assert.equal(success, true);
return tokenInstance.transferFrom(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(receipt){
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Transfer", 'should be "Transfer" event');
assert.equal(receipt.logs[0].args._from, fromAccount, "logs the account the tokens are transferred from");
assert.equal(receipt.logs[0].args._to, toAccount, "logs the account the tokens are transferred to");
assert.equal(receipt.logs[0].args._value, 10, "logs the transfer amount");
return tokenInstance.balanceOf(fromAccount);
}).then(function(balance){
assert.equal(balance.toNumber(), 90, 'left amount after deduction');
return tokenInstance.balanceOf(toAccount);
}).then(function(balance){
assert.equal(balance.toNumber(), 10, 'adds the amount from the recieving account');
return tokenInstance.allowance(fromAccount, spendingAccount);
}).then(function(allowance) {
assert.equal(allowance.toNumber(), 0, 'deducts the amount from the allowance');
})
});
});
contract("DappToken", function(accounts) {
var tokenInstance;
it("initialize the contract with correct values", function() {
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
return tokenInstance.name();
})
.then(function(name) {
assert.equal(name, "DApp Token", "has the correct name");
return tokenInstance.symbol();
})
.then(function(symbol) {
assert.equal(symbol, "DAPP", "has the correct symbol");
return tokenInstance.standard();
})
.then(function(standard) {
assert.equal(standard, "DApp Token v1.0", "has the correct standard");
});
});
it("allocates the initial supply supply upon deployment", function() {
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
return tokenInstance.totalSupply();
})
.then(function(totalSupply) {
assert.equal(totalSupply.toNumber(), 1000000, "sets the total supply to 1,000,000");
return tokenInstance.balanceOf(accounts[0]);
})
.then(function(adminBalance) {
assert.equal(adminBalance.toNumber(), 1000000, "it allocates the intial supply to the admin account");
});
});
it("transfer token ownership", function() {
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
// console.log(accounts[1])
return tokenInstance.transfer.call(accounts[1], 999999999999);
})
.then(assert.fail)
.catch(function(error) {
// console.log(error.message)
assert(error.message.indexOf("revert") >= 0, "error message must contain revert");
return tokenInstance.transfer.call(accounts[1], 250000, { from: accounts[0] });
})
.then(function(success) {
assert.equal(success, true, "it return true");
return tokenInstance.transfer(accounts[1], 250000, { from: accounts[0] });
})
.then(function(receipt) {
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Transfer", 'should be "Transfer event');
assert.equal(receipt.logs[0].args._from, accounts[0], "logs the account the tokens are transferred from");
assert.equal(receipt.logs[0].args._to, accounts[1], "logs the account the tokens are transferred to");
assert.equal(receipt.logs[0].args._value, 250000, "logs the transfer amount");
return tokenInstance.balanceOf(accounts[1]);
})
.then(function(balance) {
assert.equal(balance.toNumber(), 250000, "adds the amount to the receiving account");
return tokenInstance.balanceOf(accounts[0]);
})
.then(function(balance) {
assert.equal(balance.toNumber(), 750000, "deducts the amount from the sending account");
});
});
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");
return tokenInstance.allowance(accounts[0], accounts[1]);
})
.then(function(allowance) {
assert.equal(allowance.toNumber(), 100, "stores the allowance for delegated transfer");
});
});
it("handles delegated token transfers", function() {
return DappToken.deployed()
.then(function(instance) {
tokenInstance = instance;
fromAccount = accounts[2];
toAccount = accounts[3];
spendingAccount = accounts[4]; // This is msg.sender
// Tranfer some tokens to fromAccount, so that it has some tokens to transfer
return tokenInstance.transfer(fromAccount, 100, { from: accounts[0] });
})
.then(function(receipt) {
// Approve spendingAccount to spend 10 tokens from fromAccount
return tokenInstance.approve(spendingAccount, 10, { from: fromAccount });
})
.then(function(receipt) {
// Try transferring something larger than the sender's balance
return tokenInstance.transferFrom(fromAccount, toAccount, 9999, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than balance");
// Try transferring something larger than the approved amount
return tokenInstance.transferFrom(fromAccount, toAccount, 20, { from: spendingAccount });
})
.then(assert.fail)
.catch(function(error) {
assert(error.message.indexOf("revert") >= 0, "cannot transfer value larger than approved amount");
return tokenInstance.transferFrom.call(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(success){
assert.equal(success, true);
return tokenInstance.transferFrom(fromAccount, toAccount, 10, { from: spendingAccount });
}).then(function(receipt){
assert.equal(receipt.logs.length, 1, "triggers one event");
assert.equal(receipt.logs[0].event, "Transfer", 'should be "Transfer" event');
assert.equal(receipt.logs[0].args._from, fromAccount, "logs the account the tokens are transferred from");
assert.equal(receipt.logs[0].args._to, toAccount, "logs the account the tokens are transferred to");
assert.equal(receipt.logs[0].args._value, 10, "logs the transfer amount");
return tokenInstance.balanceOf(fromAccount);
}).then(function(balance){
assert.equal(balance.toNumber(), 90, 'left amount after deduction');
return tokenInstance.balanceOf(toAccount);
}).then(function(balance){
assert.equal(balance.toNumber(), 10, 'adds the amount from the recieving account');
return tokenInstance.allowance(fromAccount, spendingAccount);
}).then(function(allowance) {
assert.equal(allowance.toNumber(), 0, 'deducts the amount from the allowance');
})
});
});