diff --git a/ethereum/contracts/CashToken.sol b/ethereum/contracts/CashToken.sol index e53044b36..61252c304 100644 --- a/ethereum/contracts/CashToken.sol +++ b/ethereum/contracts/CashToken.sol @@ -51,14 +51,16 @@ contract CashToken is ICash { /// @notice The amount of cash principal per account mapping (address => uint128) public cashPrincipal; + bool public initialized = false; + /** * @notice Construct a Cash Token * @dev You must call `initialize()` after construction * @param admin_ The address of admin */ - constructor(address admin_) { + constructor(address admin_) { admin = admin_; - } + } /** * @notice Initialize Cash token contract @@ -66,11 +68,13 @@ contract CashToken is ICash { * @param initialYieldStart The timestamp when Cash index and yield were activated on Gateway */ function initialize(uint128 initialYield, uint initialYieldStart) external { - require(cashYieldAndIndex.index == 0, "Cash Token already initialized"); + require(initialized == false, "Cash Token already initialized"); // Note: we don't check that this is in the past, but calls will revert until it is. cashYieldStart = initialYieldStart; cashYieldAndIndex = CashYieldAndIndex({yield: initialYield, index: 1e18}); + + initialized = true; } /** @@ -121,6 +125,8 @@ contract CashToken is ICash { */ function setFutureYield(uint128 nextYield, uint128 nextIndex, uint nextYieldStart) external override { require(msg.sender == admin, "Must be admin"); + require(nextYield <= 1e4, "Invalid yield range"); + require(nextYieldStart > cashYieldStart, "Invalid yield start"); uint nextStart = nextCashYieldStart; // Updating cash yield and index to the 'old' next values @@ -130,6 +136,8 @@ contract CashToken is ICash { } nextCashYieldStart = nextYieldStart; nextCashYieldAndIndex = CashYieldAndIndex({yield: nextYield, index: nextIndex}); + + emit SetFutureYield(nextYield, nextIndex, nextYieldStart); } /** diff --git a/ethereum/contracts/ICash.sol b/ethereum/contracts/ICash.sol index 800a559c3..ac597b170 100644 --- a/ethereum/contracts/ICash.sol +++ b/ethereum/contracts/ICash.sol @@ -25,6 +25,8 @@ interface ICash is IERC20 { function burn(address account, uint amount) external returns (uint128); function setFutureYield(uint128 nextYield, uint128 nextIndex, uint nextYieldStartAt) external; function getCashIndex() external view returns (uint128); + + event SetFutureYield(uint128 nextCashYield, uint128 nextCashYieldIndex, uint nextCashYieldStart); } /** diff --git a/ethereum/contracts/Starport.sol b/ethereum/contracts/Starport.sol index 63f39d853..e8e356d80 100644 --- a/ethereum/contracts/Starport.sol +++ b/ethereum/contracts/Starport.sol @@ -96,7 +96,7 @@ contract Starport { * @dev Externally-owned accounts may call `execTrxRequest` with a signed message to avoid Ethereum fees. * @param trxRequest An ASCII-encoded transaction request */ - function execTrxRequest(string calldata trxRequest) public payable { + function execTrxRequest(string calldata trxRequest) public { emit ExecTrxRequest(msg.sender, trxRequest); } diff --git a/ethereum/contracts/test/CashToken2.sol b/ethereum/contracts/test/CashToken2.sol index f0f549023..3937e0d37 100644 --- a/ethereum/contracts/test/CashToken2.sol +++ b/ethereum/contracts/test/CashToken2.sol @@ -4,16 +4,16 @@ pragma solidity ^0.8.1; import "../CashToken.sol"; contract CashToken2 is CashToken { - bool public intiailized_ = false; + bool public initialized_ = false; uint public counter = 0; constructor(address admin_) CashToken(admin_) { } function initialize_(uint counter_) public { - require(intiailized_ == false, "cannot reinitialize"); + require(initialized_ == false, "cannot reinitialize"); counter = counter_; - intiailized_ = true; + initialized_ = true; } /// Simple function to test notices diff --git a/ethereum/tests/cash_token_test.js b/ethereum/tests/cash_token_test.js index 1a44d23af..a0e521157 100644 --- a/ethereum/tests/cash_token_test.js +++ b/ethereum/tests/cash_token_test.js @@ -41,9 +41,11 @@ describe('CashToken', () => { expect(await call(cash, 'admin')).toMatchAddress(admin); let cashYieldAndIndex = await call(cash, 'cashYieldAndIndex'); let cashYieldStart = await call(cash, 'cashYieldStart'); + let initialized = await call(cash, 'initialized'); expect(cashYieldAndIndex.index).toEqualNumber(1e18); expect(cashYieldAndIndex.yield).toEqualNumber(0); expect(cashYieldStart).toEqualNumber(start); + expect(initialized).toEqual(true); }); it('should have correct admin and yield references when non-zero', async () => { @@ -172,7 +174,7 @@ describe('CashToken', () => { const start_before = await call(cash, 'cashYieldStart'); // Update future yield, first change - await send(cash, 'setFutureYield', [43628, 1e6, nextYieldTimestamp], { from: admin }); + await send(cash, 'setFutureYield', [362, 1e6, nextYieldTimestamp], { from: admin }); const yieldAndIndex_change = await call(cash, 'cashYieldAndIndex'); const start_change = await call(cash, 'cashYieldStart'); const nextYieldAndIndex_change = await call(cash, 'nextCashYieldAndIndex'); @@ -181,14 +183,14 @@ describe('CashToken', () => { expect(yieldAndIndex_change.yield).toEqualNumber(yieldAndIndex_before.yield); expect(yieldAndIndex_change.index).toEqualNumber(yieldAndIndex_before.index); expect(start_change).toEqualNumber(start_before); - expect(nextYieldAndIndex_change.yield).toEqualNumber(43628); + expect(nextYieldAndIndex_change.yield).toEqualNumber(362); expect(nextYieldAndIndex_change.index).toEqualNumber(1e6); expect(nextStart_change).toEqualNumber(nextYieldTimestamp); await sendRPC(web3, "evm_increaseTime", [31 * 60]); // Update future yield, second change, current yield, index and time are set to previous next values - await send(cash, 'setFutureYield', [43629, 11e5, nextYieldTimestamp + 60 * 60], { from: admin }); + await send(cash, 'setFutureYield', [369, 11e5, nextYieldTimestamp + 60 * 60], { from: admin }); const yieldAndIndex_change2 = await call(cash, 'cashYieldAndIndex'); const start_change2 = await call(cash, 'cashYieldStart'); const nextYieldAndIndex_change2 = await call(cash, 'nextCashYieldAndIndex'); @@ -197,7 +199,7 @@ describe('CashToken', () => { expect(yieldAndIndex_change2.yield).toEqualNumber(nextYieldAndIndex_change.yield); expect(yieldAndIndex_change2.index).toEqualNumber(nextYieldAndIndex_change.index); expect(start_change2).toEqualNumber(nextStart_change); - expect(nextYieldAndIndex_change2.yield).toEqualNumber(43629); + expect(nextYieldAndIndex_change2.yield).toEqualNumber(369); expect(nextYieldAndIndex_change2.index).toEqualNumber(11e5); expect(nextStart_change2).toEqualNumber(nextYieldTimestamp + 60 * 60); }); @@ -206,8 +208,21 @@ describe('CashToken', () => { const blockNumber = await web3.eth.getBlockNumber(); const block = await web3.eth.getBlock(blockNumber); const nextYieldTimestamp = block.timestamp + 30 * 60; - await expect(send(cash, 'setFutureYield', [43628, 1e6, nextYieldTimestamp], { from: account1 })).rejects.toRevert("revert Must be admin"); - }) + await expect(send(cash, 'setFutureYield', [300, 1e6, nextYieldTimestamp], { from: account1 })).rejects.toRevert("revert Must be admin"); + }); + + it('should fail if next yield start is before current yield start', async() => { + const start_yield = await call(cash, 'cashYieldStart'); + await expect(send(cash, 'setFutureYield', [300, 1e6, start_yield], { from: admin })).rejects.toRevert("revert Invalid yield start"); + await expect(send(cash, 'setFutureYield', [300, 1e6, start_yield - 1000], { from: admin })).rejects.toRevert("revert Invalid yield start"); + }); + + it('should fail if yield range is invalid', async() => { + const blockNumber = await web3.eth.getBlockNumber(); + const block = await web3.eth.getBlock(blockNumber); + const nextYieldTimestamp = block.timestamp + 30 * 60; + await expect(send(cash, 'setFutureYield', [30000, 1e6, nextYieldTimestamp], { from: admin })).rejects.toRevert("revert Invalid yield range"); + }); }); describe('#mint', () => { diff --git a/ethereum/tests/starport_test.js b/ethereum/tests/starport_test.js index bdcc44c24..dc6931e9e 100644 --- a/ethereum/tests/starport_test.js +++ b/ethereum/tests/starport_test.js @@ -1485,11 +1485,13 @@ describe('Starport', () => { const tx = await send(starport, 'setFutureYield', [nextCashYield, nextCashYieldIndex, nextCashYieldStart], { from: root }); - expect(tx.events.SetFutureYield.returnValues).toMatchObject({ + const expectedYieldEvent = { nextCashYield: nextCashYield.toString(), nextCashYieldIndex: nextCashYieldIndex.toString(), nextCashYieldStart: nextCashYieldStart.toString(), - }); + }; + expect(tx.events.SetFutureYield[0].returnValues).toMatchObject(expectedYieldEvent); + expect(tx.events.SetFutureYield[1].returnValues).toMatchObject(expectedYieldEvent); expect(await call(cash, 'cashYieldAndIndex')).toMatchObject({ yield: "0", @@ -1522,11 +1524,13 @@ describe('Starport', () => { index: "1234", }); - expect(tx.events.SetFutureYield.returnValues).toMatchObject({ + const expectedYieldEvent = { nextCashYield: nextCashYield.toString(), nextCashYieldIndex: nextCashYieldIndex.toString(), nextCashYieldStart: nextCashYieldStart.toString(), - }); + }; + expect(tx.events.SetFutureYield[0].returnValues).toMatchObject(expectedYieldEvent); + expect(tx.events.SetFutureYield[1].returnValues).toMatchObject(expectedYieldEvent); }); it('should set future yield via hand-coded notice', async () => { @@ -1543,11 +1547,13 @@ describe('Starport', () => { const signatures = signAll(setFutureYieldNotice, authorityWallets); const tx = await send(starport, 'invoke', [setFutureYieldNotice, signatures], { from: account1 }); - expect(tx.events.SetFutureYield.returnValues).toMatchObject({ + const expectedYieldEvent = { nextCashYield: "1200", nextCashYieldIndex: "1234", nextCashYieldStart: "1644703495", - }); + } + expect(tx.events.SetFutureYield[0].returnValues).toMatchObject(expectedYieldEvent); + expect(tx.events.SetFutureYield[1].returnValues).toMatchObject(expectedYieldEvent); }); it('should fail when not called by self or admin', async () => {