diff --git a/contracts/LDGX.sol b/contracts/LDGX.sol index a8d7455..4aba94b 100644 --- a/contracts/LDGX.sol +++ b/contracts/LDGX.sol @@ -23,6 +23,8 @@ contract LDGX is StandardToken { address public DGX_TOKEN_STORAGE; bool public initialized; + event Transfer(address indexed from, address indexed to, uint256 value, bytes32 indexed data); + constructor(address _dgxTokenAddress, address _dgxTokenStorage) public { totalSupply_ = 0; DGX_TOKEN_ADDRESS = _dgxTokenAddress; @@ -38,6 +40,18 @@ contract LDGX is StandardToken { require(success); } + // transfer function to support ERC-223 + function transfer(address _to, uint256 _value, bytes32 _data) + public + returns (bool _success) + { + if(isContract(_to)) { + _success = transferToContract(_to, _value, _data); + } else { + _success = transferToAddress(_to, _value, _data); + } + } + // first deposit to make sure there is already some DGX/LDGX function firstDeposit(uint256 _dgxIn) public { require(!initialized); @@ -111,4 +125,41 @@ contract LDGX is StandardToken { { _dgxLdgx = totalSupply_.mul(10**9).div(ERC20(DGX_TOKEN_ADDRESS).balanceOf(address(this))); } + + ////////////////////////// PRIVATE FUNCTIONS //////////////////////////////// + + function transferToContract(address _to, uint256 _value, bytes32 _data) + private + returns (bool _success) + { + require(balances[msg.sender] >= _value); + balances[msg.sender] = balances[msg.sender].sub(_value); + balances[_to] = balances[_to].add(_value); + TokenReceiver(_to).tokenFallback(msg.sender, _value, _data); + emit Transfer(msg.sender, _to, _value, _data); + _success = true; + } + + function transferToAddress(address _to, uint256 _value, bytes32 _data) + private + returns (bool _success) + { + require(balances[msg.sender] >= _value); + balances[msg.sender] = balances[msg.sender].sub(_value); + balances[_to] = balances[_to].add(_value); + emit Transfer(msg.sender, _to, _value, _data); + _success = true; + } + + function isContract(address _address) + private + constant + returns (bool) + { + uint length; + assembly { + length := extcodesize(_address) + } + return (length > 0); + } } diff --git a/contracts/mock/MockContractWithFallback.sol b/contracts/mock/MockContractWithFallback.sol new file mode 100644 index 0000000..86c4b9c --- /dev/null +++ b/contracts/mock/MockContractWithFallback.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.4.23; + +contract MockContractWithFallback { + bytes32 public latestData; + function tokenFallback(address _sender, uint256 _value, bytes32 _data) + public + returns (bool _success) + { + latestData = _data; + _success = true; + } +} diff --git a/contracts/mock/MockContractWithoutFallback.sol b/contracts/mock/MockContractWithoutFallback.sol new file mode 100644 index 0000000..bb9379a --- /dev/null +++ b/contracts/mock/MockContractWithoutFallback.sol @@ -0,0 +1,9 @@ +pragma solidity ^0.4.23; + +contract MockContractWithoutFallback { + bytes32 public latestData; + + constructor() public { + latestData = "a"; + } +} diff --git a/test/LDGX.js b/test/LDGX.js index 2f802d3..56f6468 100644 --- a/test/LDGX.js +++ b/test/LDGX.js @@ -1,6 +1,8 @@ const a = require('awaiting'); const LDGX = artifacts.require('./LDGX.sol'); +const MockValidContract = artifacts.require('./MockContractWithFallback.sol'); +const MockInvalidContract = artifacts.require('./MockContractWithoutFallback.sol'); const { deployLibraries, @@ -218,6 +220,56 @@ contract('Lite DGX', function (accounts) { }); }); + describe('EIP223 transfer', function () { + let ldgxBalance; + let validContract; + let invalidContract; + before(async function () { + // deposit some DGX to get LDGX + await contracts.dgx.transferAndCall(contracts.liteDgx.address, bN(1e9), '', { from: addressOf.testUser1 }); + ldgxBalance = await contracts.liteDgx.balanceOf.call(addressOf.testUser1); + // deploy necessary mock contracts + validContract = await MockValidContract.new(); + invalidContract = await MockInvalidContract.new(); + }); + it('[can transfer to valid contract]', async function () { + const initialBalance = await contracts.liteDgx.balanceOf.call(addressOf.testUser1); + const transferAmount = bN(200); + assert.ok(await contracts.liteDgx.transfer.call( + validContract.address, + transferAmount, + '', + { from: addressOf.testUser1 }, + )); + await contracts.liteDgx.transfer(validContract.address, transferAmount, '', { from: addressOf.testUser1 }); + assert.deepEqual(await contracts.liteDgx.balanceOf.call(validContract.address), transferAmount); + assert.deepEqual(await contracts.liteDgx.balanceOf.call(addressOf.testUser1), initialBalance.minus(transferAmount)); + }); + it('[can transfer to any account address]', async function () { + const initialBalance = await contracts.liteDgx.balanceOf.call(addressOf.testUser1); + const transferAmount = bN(200); + const recipient = randomAddress(); + assert.ok(await contracts.liteDgx.transfer.call( + recipient, + transferAmount, + '', + { from: addressOf.testUser1 }, + )); + await contracts.liteDgx.transfer(recipient, transferAmount, '', { from: addressOf.testUser1 }); + assert.deepEqual(await contracts.liteDgx.balanceOf.call(recipient), transferAmount); + assert.deepEqual(await contracts.liteDgx.balanceOf.call(addressOf.testUser1), initialBalance.minus(transferAmount)); + }); + it('[cannot transfer to contract without tokenFallback]: revert', async function () { + const transferAmount = bN(200); + assert(await a.failure(contracts.liteDgx.transfer.call( + invalidContract.address, + transferAmount, + '', + { from: addressOf.testUser1 }, + ))); + }); + }); + describe('withdraw DGX', function () { it('[withdraw more than ldgx balance]: revert', async function () { assert(await a.failure(contracts.liteDgx.withdraw.call(