Stop ERC-20 transfer bug when to/from accounts are the same (#425)

Using the ERC-20 sample, you can submit a transfer to and from
the same account. Because the code doesn't handle this, it ends
up minting new tokens into that account.

The correct behaviour is not specified by the ERC-20 specification,
although the OpenZeppelin implementation seems to permit it.

IMO we should just block it with an error because I can't see a use
case for allowing it and it is most likely a user error.

Signed-off-by: Simon Stone <sstone1@uk.ibm.com>
This commit is contained in:
Simon Stone 2021-02-24 20:07:06 +00:00 committed by GitHub
parent 651f2235aa
commit 365172ddb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 14 additions and 0 deletions

View file

@ -427,6 +427,10 @@ func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface
// Dependant functions include Transfer and TransferFrom // Dependant functions include Transfer and TransferFrom
func transferHelper(ctx contractapi.TransactionContextInterface, from string, to string, value int) error { func transferHelper(ctx contractapi.TransactionContextInterface, from string, to string, value int) error {
if from == to {
return fmt.Errorf("cannot transfer to and from same client account")
}
if value < 0 { // transfer of 0 is allowed in ERC-20, so just validate against negative amounts if value < 0 { // transfer of 0 is allowed in ERC-20, so just validate against negative amounts
return fmt.Errorf("transfer amount cannot be negative") return fmt.Errorf("transfer amount cannot be negative")
} }

View file

@ -161,6 +161,10 @@ class TokenERC20Contract extends Contract {
async _transfer(ctx, from, to, value) { async _transfer(ctx, from, to, value) {
if (from === to) {
throw new Error('cannot transfer to and from same client account');
}
// Convert value from string to int // Convert value from string to int
const valueInt = parseInt(value); const valueInt = parseInt(value);

View file

@ -94,6 +94,12 @@ describe('Chaincode', () => {
}); });
describe('#_transfer', () => { describe('#_transfer', () => {
it('should fail when the sender and the receipient are the same', async () => {
await expect(token._transfer(ctx, 'Alice', 'Alice', '1000'))
.to.be.rejectedWith(Error, 'cannot transfer to and from same client account');
});
it('should fail when the sender does not have enough token', async () => { it('should fail when the sender does not have enough token', async () => {
mockStub.createCompositeKey.withArgs('balance', ['Alice']).returns('balance_Alice'); mockStub.createCompositeKey.withArgs('balance', ['Alice']).returns('balance_Alice');
mockStub.getState.withArgs('balance_Alice').resolves(Buffer.from('500')); mockStub.getState.withArgs('balance_Alice').resolves(Buffer.from('500'));