From 8e4578f3a284b0a4e3b4f1a459bc5145babd458e Mon Sep 17 00:00:00 2001 From: Antonina Norair Date: Tue, 16 Mar 2021 12:55:25 -0700 Subject: [PATCH 1/7] Contract changes --- ethereum/contracts/CashToken.sol | 25 ++++++++++++++++++++++--- ethereum/contracts/ICash.sol | 4 ++++ ethereum/contracts/Starport.sol | 4 +--- ethereum/contracts/test/CashToken2.sol | 6 +++--- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/ethereum/contracts/CashToken.sol b/ethereum/contracts/CashToken.sol index e53044b36..28cc15e49 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; } /** @@ -130,6 +134,8 @@ contract CashToken is ICash { } nextCashYieldStart = nextYieldStart; nextCashYieldAndIndex = CashYieldAndIndex({yield: nextYield, index: nextIndex}); + + emit SetFutureYield(nextYield, nextIndex, nextYieldStart); } /** @@ -179,6 +185,19 @@ contract CashToken is ICash { return true; } + /** + * @notice Moves `amount` tokens from the caller's account to `recipient`. + * @return a boolean value indicating whether the operation succeeded. + * Emits a {Transfer} event. + */ + function transferPrincipal(address recipient, uint128 principal) external override returns (bool) { + require(msg.sender != recipient, "Invalid recipient"); + cashPrincipal[recipient] += principal; + cashPrincipal[msg.sender] -= principal; + emit TransferPrincipal(msg.sender, recipient, principal); + return true; + } + /** * @notice Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is diff --git a/ethereum/contracts/ICash.sol b/ethereum/contracts/ICash.sol index 800a559c3..9d7a0ca8f 100644 --- a/ethereum/contracts/ICash.sol +++ b/ethereum/contracts/ICash.sol @@ -23,8 +23,12 @@ interface IERC20 { interface ICash is IERC20 { function mint(address account, uint128 principal) external returns (uint); function burn(address account, uint amount) external returns (uint128); + function transferPrincipal(address recipient, uint128 principal) external returns (bool); function setFutureYield(uint128 nextYield, uint128 nextIndex, uint nextYieldStartAt) external; function getCashIndex() external view returns (uint128); + + event SetFutureYield(uint128 nextCashYield, uint128 nextCashYieldIndex, uint nextCashYieldStart); + event TransferPrincipal(address indexed from, address indexed to, uint128 value); } /** diff --git a/ethereum/contracts/Starport.sol b/ethereum/contracts/Starport.sol index 63f39d853..0b0f3a206 100644 --- a/ethereum/contracts/Starport.sol +++ b/ethereum/contracts/Starport.sol @@ -31,7 +31,6 @@ contract Starport { event Unlock(address indexed account, uint amount, address asset); event UnlockCash(address indexed account, uint amount, uint128 principal); event ChangeAuthorities(address[] newAuthorities); - event SetFutureYield(uint128 nextCashYield, uint128 nextCashYieldIndex, uint nextCashYieldStart); event ExecuteProposal(string title, bytes[] extrinsics); event NewSupplyCap(address indexed asset, uint supplyCap); @@ -96,7 +95,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); } @@ -339,7 +338,6 @@ contract Starport { function setFutureYield(uint128 nextCashYield, uint128 nextCashYieldIndex, uint nextCashYieldStart) external { require(msg.sender == address(this) || msg.sender == admin, "Call must be by notice or admin"); - emit SetFutureYield(nextCashYield, nextCashYieldIndex, nextCashYieldStart); cash.setFutureYield(nextCashYield, nextCashYieldIndex, nextCashYieldStart); } 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 From 88c7089d70f8f9b21b670c34023fe7bb76b24f36 Mon Sep 17 00:00:00 2001 From: Antonina Norair Date: Tue, 16 Mar 2021 13:00:00 -0700 Subject: [PATCH 2/7] Update comment --- ethereum/contracts/CashToken.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/contracts/CashToken.sol b/ethereum/contracts/CashToken.sol index 28cc15e49..154abfa79 100644 --- a/ethereum/contracts/CashToken.sol +++ b/ethereum/contracts/CashToken.sol @@ -186,9 +186,9 @@ contract CashToken is ICash { } /** - * @notice Moves `amount` tokens from the caller's account to `recipient`. + * @notice Moves Cash `principal` from the caller's account to `recipient`. * @return a boolean value indicating whether the operation succeeded. - * Emits a {Transfer} event. + * Emits a {TransferPrincipal} event. */ function transferPrincipal(address recipient, uint128 principal) external override returns (bool) { require(msg.sender != recipient, "Invalid recipient"); From 4dea7f22ff1c9bacff7b78c35bd34bb9c6539ffd Mon Sep 17 00:00:00 2001 From: Antonina Norair Date: Tue, 16 Mar 2021 14:53:23 -0700 Subject: [PATCH 3/7] Fix tests --- ethereum/contracts/Starport.sol | 2 ++ ethereum/tests/cash_token_test.js | 2 ++ ethereum/tests/starport_test.js | 18 ++++++++++++------ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ethereum/contracts/Starport.sol b/ethereum/contracts/Starport.sol index 0b0f3a206..e8e356d80 100644 --- a/ethereum/contracts/Starport.sol +++ b/ethereum/contracts/Starport.sol @@ -31,6 +31,7 @@ contract Starport { event Unlock(address indexed account, uint amount, address asset); event UnlockCash(address indexed account, uint amount, uint128 principal); event ChangeAuthorities(address[] newAuthorities); + event SetFutureYield(uint128 nextCashYield, uint128 nextCashYieldIndex, uint nextCashYieldStart); event ExecuteProposal(string title, bytes[] extrinsics); event NewSupplyCap(address indexed asset, uint supplyCap); @@ -338,6 +339,7 @@ contract Starport { function setFutureYield(uint128 nextCashYield, uint128 nextCashYieldIndex, uint nextCashYieldStart) external { require(msg.sender == address(this) || msg.sender == admin, "Call must be by notice or admin"); + emit SetFutureYield(nextCashYield, nextCashYieldIndex, nextCashYieldStart); cash.setFutureYield(nextCashYield, nextCashYieldIndex, nextCashYieldStart); } diff --git a/ethereum/tests/cash_token_test.js b/ethereum/tests/cash_token_test.js index 1a44d23af..8886c2eb3 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 () => { 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 () => { From 56b8cd6a13718fe690bbbc50c7401ee1024ab5c6 Mon Sep 17 00:00:00 2001 From: Antonina Norair Date: Tue, 16 Mar 2021 15:14:29 -0700 Subject: [PATCH 4/7] Transfer principal tests, just in case --- ethereum/tests/cash_token_test.js | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/ethereum/tests/cash_token_test.js b/ethereum/tests/cash_token_test.js index 8886c2eb3..fd8fa0f49 100644 --- a/ethereum/tests/cash_token_test.js +++ b/ethereum/tests/cash_token_test.js @@ -414,6 +414,44 @@ describe('CashToken', () => { }); }); + describe('#transferPrincipal', () => { + it('should transfer Cash principal between users', async() => { + // Mint tokes first to have something to transfer + const cashIndex = await call(cash, 'getCashIndex'); + const principal = 10e6; + await send(cash, 'mint', [account1, principal], { from: admin }); + const amount = principal * cashIndex / 1e18; + + const transferPrincipal = 5e6; + const tx = await send(cash, 'transferPrincipal', [account2, 5e6], { from: account1 }); + expect(tx.events.TransferPrincipal.returnValues).toMatchObject({ + from: account1, + to: account2, + value: transferPrincipal.toString() + }); + + expect(await call(cash, 'totalSupply')).toEqualNumber(amount); + expect(await call(cash, 'cashPrincipal', [account1])).toEqualNumber(5e6); + expect(await call(cash, 'cashPrincipal', [account2])).toEqualNumber(5e6); + expect(await call(cash, 'balanceOf', [account1])).toEqualNumber(amount / 2); + expect(await call(cash, 'balanceOf', [account2])).toEqualNumber(amount / 2); + }); + + it('should fail if recipient is invalid', async() => { + await expect(send(cash, 'transferPrincipal', [account1, 1e6], { from: account1 })).rejects.toRevert("revert Invalid recipient"); + }); + + it('should fail if not enough Cash principal to transfer', async() => { + const cashIndex = await call(cash, 'getCashIndex'); + const principal = 10e6; + await send(cash, 'mint', [account1, principal], { from: admin }); + + const amount = principal * cashIndex / 1e18; + // An attempt to transfer double amount + await expect(send(cash, 'transferPrincipal', [account2, 2 * principal], { from: account1 })).rejects.toRevert("revert"); + }); + }); + describe("#getCashIndex tests", () => { it('getCashIndex is growing over time', async() => { const blockNumber = await web3.eth.getBlockNumber(); From 18e1f67763da617c2d507b7c2c327099badfd890 Mon Sep 17 00:00:00 2001 From: Antonina Norair Date: Tue, 16 Mar 2021 16:06:18 -0700 Subject: [PATCH 5/7] Yield checks --- ethereum/contracts/CashToken.sol | 2 ++ ethereum/tests/cash_token_test.js | 25 +++++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ethereum/contracts/CashToken.sol b/ethereum/contracts/CashToken.sol index 154abfa79..2a25688c5 100644 --- a/ethereum/contracts/CashToken.sol +++ b/ethereum/contracts/CashToken.sol @@ -125,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 diff --git a/ethereum/tests/cash_token_test.js b/ethereum/tests/cash_token_test.js index fd8fa0f49..505a0d4bd 100644 --- a/ethereum/tests/cash_token_test.js +++ b/ethereum/tests/cash_token_test.js @@ -174,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'); @@ -183,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'); @@ -199,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); }); @@ -208,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', () => { From 64dcf471332115320ecf251dda54639b486d5f12 Mon Sep 17 00:00:00 2001 From: Antonina Norair Date: Tue, 16 Mar 2021 16:15:55 -0700 Subject: [PATCH 6/7] Rename value to principal --- ethereum/contracts/ICash.sol | 2 +- ethereum/tests/cash_token_test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/contracts/ICash.sol b/ethereum/contracts/ICash.sol index 9d7a0ca8f..967d31e9f 100644 --- a/ethereum/contracts/ICash.sol +++ b/ethereum/contracts/ICash.sol @@ -28,7 +28,7 @@ interface ICash is IERC20 { function getCashIndex() external view returns (uint128); event SetFutureYield(uint128 nextCashYield, uint128 nextCashYieldIndex, uint nextCashYieldStart); - event TransferPrincipal(address indexed from, address indexed to, uint128 value); + event TransferPrincipal(address indexed from, address indexed to, uint128 principal); } /** diff --git a/ethereum/tests/cash_token_test.js b/ethereum/tests/cash_token_test.js index 505a0d4bd..0729d3c8d 100644 --- a/ethereum/tests/cash_token_test.js +++ b/ethereum/tests/cash_token_test.js @@ -440,7 +440,7 @@ describe('CashToken', () => { expect(tx.events.TransferPrincipal.returnValues).toMatchObject({ from: account1, to: account2, - value: transferPrincipal.toString() + principal: transferPrincipal.toString() }); expect(await call(cash, 'totalSupply')).toEqualNumber(amount); From e9a04d11df711ceec1f0e0ac1443c07916c10f67 Mon Sep 17 00:00:00 2001 From: Antonina Norair Date: Tue, 16 Mar 2021 18:04:08 -0700 Subject: [PATCH 7/7] Remove transferPrincipal --- ethereum/contracts/CashToken.sol | 13 ----------- ethereum/contracts/ICash.sol | 2 -- ethereum/tests/cash_token_test.js | 38 ------------------------------- 3 files changed, 53 deletions(-) diff --git a/ethereum/contracts/CashToken.sol b/ethereum/contracts/CashToken.sol index 2a25688c5..61252c304 100644 --- a/ethereum/contracts/CashToken.sol +++ b/ethereum/contracts/CashToken.sol @@ -187,19 +187,6 @@ contract CashToken is ICash { return true; } - /** - * @notice Moves Cash `principal` from the caller's account to `recipient`. - * @return a boolean value indicating whether the operation succeeded. - * Emits a {TransferPrincipal} event. - */ - function transferPrincipal(address recipient, uint128 principal) external override returns (bool) { - require(msg.sender != recipient, "Invalid recipient"); - cashPrincipal[recipient] += principal; - cashPrincipal[msg.sender] -= principal; - emit TransferPrincipal(msg.sender, recipient, principal); - return true; - } - /** * @notice Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is diff --git a/ethereum/contracts/ICash.sol b/ethereum/contracts/ICash.sol index 967d31e9f..ac597b170 100644 --- a/ethereum/contracts/ICash.sol +++ b/ethereum/contracts/ICash.sol @@ -23,12 +23,10 @@ interface IERC20 { interface ICash is IERC20 { function mint(address account, uint128 principal) external returns (uint); function burn(address account, uint amount) external returns (uint128); - function transferPrincipal(address recipient, uint128 principal) external returns (bool); function setFutureYield(uint128 nextYield, uint128 nextIndex, uint nextYieldStartAt) external; function getCashIndex() external view returns (uint128); event SetFutureYield(uint128 nextCashYield, uint128 nextCashYieldIndex, uint nextCashYieldStart); - event TransferPrincipal(address indexed from, address indexed to, uint128 principal); } /** diff --git a/ethereum/tests/cash_token_test.js b/ethereum/tests/cash_token_test.js index 0729d3c8d..a0e521157 100644 --- a/ethereum/tests/cash_token_test.js +++ b/ethereum/tests/cash_token_test.js @@ -427,44 +427,6 @@ describe('CashToken', () => { }); }); - describe('#transferPrincipal', () => { - it('should transfer Cash principal between users', async() => { - // Mint tokes first to have something to transfer - const cashIndex = await call(cash, 'getCashIndex'); - const principal = 10e6; - await send(cash, 'mint', [account1, principal], { from: admin }); - const amount = principal * cashIndex / 1e18; - - const transferPrincipal = 5e6; - const tx = await send(cash, 'transferPrincipal', [account2, 5e6], { from: account1 }); - expect(tx.events.TransferPrincipal.returnValues).toMatchObject({ - from: account1, - to: account2, - principal: transferPrincipal.toString() - }); - - expect(await call(cash, 'totalSupply')).toEqualNumber(amount); - expect(await call(cash, 'cashPrincipal', [account1])).toEqualNumber(5e6); - expect(await call(cash, 'cashPrincipal', [account2])).toEqualNumber(5e6); - expect(await call(cash, 'balanceOf', [account1])).toEqualNumber(amount / 2); - expect(await call(cash, 'balanceOf', [account2])).toEqualNumber(amount / 2); - }); - - it('should fail if recipient is invalid', async() => { - await expect(send(cash, 'transferPrincipal', [account1, 1e6], { from: account1 })).rejects.toRevert("revert Invalid recipient"); - }); - - it('should fail if not enough Cash principal to transfer', async() => { - const cashIndex = await call(cash, 'getCashIndex'); - const principal = 10e6; - await send(cash, 'mint', [account1, principal], { from: admin }); - - const amount = principal * cashIndex / 1e18; - // An attempt to transfer double amount - await expect(send(cash, 'transferPrincipal', [account2, 2 * principal], { from: account1 })).rejects.toRevert("revert"); - }); - }); - describe("#getCashIndex tests", () => { it('getCashIndex is growing over time', async() => { const blockNumber = await web3.eth.getBlockNumber();