Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement OdisPayments.sol and unit tests (#9740)
* Implement OdisPayments.sol and unit tests * Update registry varname because of UsingRegistryV2 changes * Add release data * Fix migration override params in package.json * Address PR comments
- Loading branch information
1 parent
c1bd057
commit 1452ccc
Showing
16 changed files
with
273 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
pragma solidity ^0.5.13; | ||
|
||
import "openzeppelin-solidity/contracts/math/SafeMath.sol"; | ||
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; | ||
import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; | ||
import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; | ||
|
||
import "./interfaces/IOdisPayments.sol"; | ||
import "../common/interfaces/ICeloVersionedContract.sol"; | ||
|
||
import "../common/Initializable.sol"; | ||
import "../common/UsingRegistryV2.sol"; | ||
import "../common/libraries/ReentrancyGuard.sol"; | ||
|
||
/** | ||
* @title Stores balance to be used for ODIS quota calculation. | ||
*/ | ||
contract OdisPayments is | ||
IOdisPayments, | ||
ICeloVersionedContract, | ||
ReentrancyGuard, | ||
Ownable, | ||
Initializable, | ||
UsingRegistryV2 | ||
{ | ||
using SafeMath for uint256; | ||
using SafeERC20 for IERC20; | ||
|
||
event PaymentMade(address indexed account, uint256 valueInCUSD); | ||
|
||
// Store amount sent (all time) from account to this contract. | ||
// Values in totalPaidCUSD should only ever be incremented, since ODIS relies | ||
// on all-time paid balance to compute every quota. | ||
mapping(address => uint256) public totalPaidCUSD; | ||
|
||
/** | ||
* @notice Sets initialized == true on implementation contracts. | ||
* @param test Set to true to skip implementation initialization. | ||
*/ | ||
constructor(bool test) public Initializable(test) {} | ||
|
||
/** | ||
* @notice Returns the storage, major, minor, and patch version of the contract. | ||
* @return Storage version of the contract. | ||
* @return Major version of the contract. | ||
* @return Minor version of the contract. | ||
* @return Patch version of the contract. | ||
*/ | ||
function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { | ||
return (1, 1, 0, 0); | ||
} | ||
|
||
/** | ||
* @notice Used in place of the constructor to allow the contract to be upgradable via proxy. | ||
*/ | ||
function initialize() external initializer { | ||
_transferOwnership(msg.sender); | ||
} | ||
|
||
/** | ||
* @notice Sends cUSD to this contract to pay for ODIS quota (for queries). | ||
* @param account The account whose balance to increment. | ||
* @param value The amount in cUSD to pay. | ||
* @dev Throws if cUSD transfer fails. | ||
*/ | ||
function payInCUSD(address account, uint256 value) external nonReentrant { | ||
IERC20(registryContract.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID)).safeTransferFrom( | ||
msg.sender, | ||
address(this), | ||
value | ||
); | ||
totalPaidCUSD[account] = totalPaidCUSD[account].add(value); | ||
emit PaymentMade(account, value); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/protocol/contracts/identity/interfaces/IOdisPayments.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pragma solidity ^0.5.13; | ||
|
||
interface IOdisPayments { | ||
function payInCUSD(address account, uint256 value) external; | ||
function totalPaidCUSD(address) external view returns (uint256); | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/protocol/contracts/identity/proxies/OdisPaymentsProxy.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pragma solidity ^0.5.13; | ||
|
||
import "../../common/Proxy.sol"; | ||
|
||
/* solhint-disable no-empty-blocks */ | ||
contract OdisPaymentsProxy is Proxy {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { CeloContractName } from '@celo/protocol/lib/registry-utils' | ||
import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils' | ||
import { OdisPaymentsInstance } from 'types' | ||
|
||
const initializeArgs = async () => { | ||
return [] | ||
} | ||
|
||
module.exports = deploymentForCoreContract<OdisPaymentsInstance>( | ||
web3, | ||
artifacts, | ||
CeloContractName.OdisPayments, | ||
initializeArgs | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
3 changes: 2 additions & 1 deletion
3
packages/protocol/releaseData/initializationData/release8.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
{ | ||
"FederatedAttestations": [] | ||
"FederatedAttestations": [], | ||
"OdisPayments": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { CeloContractName } from '@celo/protocol/lib/registry-utils' | ||
import { | ||
assertEqualBN, | ||
assertLogMatches2, | ||
assertRevert, | ||
assumeOwnership, | ||
} from '@celo/protocol/lib/test-utils' | ||
import { getDeployedProxiedContract } from '@celo/protocol/lib/web3-utils' | ||
import { fixed1 } from '@celo/utils/src/fixidity' | ||
import { | ||
FreezerContract, | ||
FreezerInstance, | ||
OdisPaymentsContract, | ||
OdisPaymentsInstance, | ||
RegistryInstance, | ||
StableTokenContract, | ||
StableTokenInstance, | ||
} from 'types' | ||
|
||
const Freezer: FreezerContract = artifacts.require('Freezer') | ||
const OdisPayments: OdisPaymentsContract = artifacts.require('OdisPayments') | ||
const StableTokenCUSD: StableTokenContract = artifacts.require('StableToken') | ||
|
||
const SECONDS_IN_A_DAY = 60 * 60 * 24 | ||
|
||
contract('OdisPayments', (accounts: string[]) => { | ||
let freezer: FreezerInstance | ||
let odisPayments: OdisPaymentsInstance | ||
let registry: RegistryInstance | ||
let stableTokenCUSD: StableTokenInstance | ||
|
||
const owner = accounts[0] | ||
const sender = accounts[1] | ||
const startingBalanceCUSD = 1000 | ||
|
||
before(async () => { | ||
// Mocking Registry.sol when using UsingRegistryV2.sol | ||
registry = await getDeployedProxiedContract('Registry', artifacts) | ||
if ((await registry.owner()) !== owner) { | ||
// In CI we need to assume ownership, locally using quicktest we don't | ||
await assumeOwnership(['Registry'], owner) | ||
} | ||
}) | ||
|
||
beforeEach(async () => { | ||
odisPayments = await OdisPayments.new(true, { from: owner }) | ||
await odisPayments.initialize() | ||
|
||
stableTokenCUSD = await StableTokenCUSD.new(true, { from: owner }) | ||
await registry.setAddressFor(CeloContractName.StableToken, stableTokenCUSD.address) | ||
await stableTokenCUSD.initialize( | ||
'Celo Dollar', | ||
'cUSD', | ||
18, | ||
registry.address, | ||
fixed1, | ||
SECONDS_IN_A_DAY, | ||
// Initialize owner and sender with balances | ||
[owner, sender], | ||
[startingBalanceCUSD, startingBalanceCUSD], | ||
'Exchange' // USD | ||
) | ||
|
||
// StableToken is freezable so this is necessary for transferFrom calls | ||
freezer = await Freezer.new(true, { from: owner }) | ||
await freezer.initialize() | ||
await registry.setAddressFor(CeloContractName.Freezer, freezer.address) | ||
}) | ||
|
||
describe('#initialize()', () => { | ||
it('should have set the owner', async () => { | ||
const actualOwner: string = await odisPayments.owner() | ||
assert.equal(actualOwner, owner) | ||
}) | ||
|
||
it('should not be callable again', async () => { | ||
await assertRevert(odisPayments.initialize()) | ||
}) | ||
}) | ||
|
||
describe('#payInCUSD', () => { | ||
const checkStateCUSD = async ( | ||
cusdSender: string, | ||
odisPaymentReceiver: string, | ||
totalValueSent: number | ||
) => { | ||
assertEqualBN( | ||
await stableTokenCUSD.balanceOf(cusdSender), | ||
startingBalanceCUSD - totalValueSent, | ||
'cusdSender balance' | ||
) | ||
assertEqualBN( | ||
await stableTokenCUSD.balanceOf(odisPayments.address), | ||
totalValueSent, | ||
'odisPayments.address balance' | ||
) | ||
assertEqualBN( | ||
await odisPayments.totalPaidCUSD(odisPaymentReceiver), | ||
totalValueSent, | ||
'odisPaymentReceiver balance' | ||
) | ||
} | ||
|
||
const valueApprovedForTransfer = 10 | ||
const receiver = accounts[2] | ||
|
||
beforeEach(async () => { | ||
await stableTokenCUSD.approve(odisPayments.address, valueApprovedForTransfer, { | ||
from: sender, | ||
}) | ||
assertEqualBN(await stableTokenCUSD.balanceOf(sender), startingBalanceCUSD) | ||
}) | ||
|
||
it('should allow sender to make a payment on their behalf', async () => { | ||
await odisPayments.payInCUSD(sender, valueApprovedForTransfer, { from: sender }) | ||
await checkStateCUSD(sender, sender, valueApprovedForTransfer) | ||
}) | ||
|
||
it('should allow sender to make a payment for another account', async () => { | ||
await odisPayments.payInCUSD(receiver, valueApprovedForTransfer, { from: sender }) | ||
await checkStateCUSD(sender, receiver, valueApprovedForTransfer) | ||
}) | ||
|
||
it('should allow sender to make multiple payments to the contract', async () => { | ||
const valueForSecondTransfer = 5 | ||
const valueForFirstTransfer = valueApprovedForTransfer - valueForSecondTransfer | ||
|
||
await odisPayments.payInCUSD(sender, valueForFirstTransfer, { from: sender }) | ||
await checkStateCUSD(sender, sender, valueForFirstTransfer) | ||
|
||
await odisPayments.payInCUSD(sender, valueForSecondTransfer, { from: sender }) | ||
await checkStateCUSD(sender, sender, valueApprovedForTransfer) | ||
}) | ||
|
||
it('should emit the PaymentMade event', async () => { | ||
const receipt = await odisPayments.payInCUSD(receiver, valueApprovedForTransfer, { | ||
from: sender, | ||
}) | ||
assertLogMatches2(receipt.logs[0], { | ||
event: 'PaymentMade', | ||
args: { | ||
account: receiver, | ||
valueInCUSD: valueApprovedForTransfer, | ||
}, | ||
}) | ||
}) | ||
|
||
it('should revert if transfer fails', async () => { | ||
await assertRevert( | ||
odisPayments.payInCUSD(sender, valueApprovedForTransfer + 1, { from: sender }) | ||
) | ||
assertEqualBN(await odisPayments.totalPaidCUSD(sender), 0) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters