Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address OZ's feedback on USDT comet support #818

Merged
merged 36 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
369b363
address L-02
Oct 29, 2023
f7bf99a
use ethcall instead of assembly
Oct 29, 2023
3544043
using low level call, so won't need IERC20Nonstandard as oppose for E…
Oct 30, 2023
85be018
add reentrancy guard, and updated tests
Nov 8, 2023
b2f8d03
address comments
Nov 8, 2023
2c72dff
inline nonReentrant call
Nov 8, 2023
499dce8
update on tests
Nov 8, 2023
3d7fd01
intent
Nov 8, 2023
79ac88a
Update test/buy-collateral-test.ts
cwang25 Nov 9, 2023
5ef9cfd
Update test/supply-test.ts
cwang25 Nov 9, 2023
6a936d1
Update test/withdraw-test.ts
cwang25 Nov 9, 2023
b3dbaa3
Update test/withdraw-test.ts
cwang25 Nov 9, 2023
ad99f2e
address comments
Nov 9, 2023
f2707f2
add low level support to approveThis as well
Nov 9, 2023
9ff7660
add bytes returndata to TransferOut/In() error, remove duplicated tests
Nov 9, 2023
41ee853
address comments, but now getting contract oversize :( both big cont…
Nov 9, 2023
8856e82
trim down a bit on contract size
Nov 10, 2023
e6d6a86
fix tests
Nov 10, 2023
9f770b4
fix lint
Nov 10, 2023
46896c9
use assembly for transfer action, as it save more spaces, and it's al…
Nov 13, 2023
9a33adc
tests update
Nov 13, 2023
f4df8a2
Add memory safe assembly
Nov 14, 2023
d688672
change reentrant to separate functions to reduce space
Nov 28, 2023
99da5c4
address comments
Nov 28, 2023
22830d0
add memory-safe as recommended in other palces
Nov 28, 2023
53376ed
address comments
Feb 6, 2024
58d1df6
lint
Feb 6, 2024
7d9dbe5
Update contracts/Comet.sol
cwang25 Feb 6, 2024
d45e8fe
Update contracts/Comet.sol
cwang25 Feb 6, 2024
b53c096
Update contracts/Comet.sol
cwang25 Feb 6, 2024
5c10dab
Update test/buy-collateral-test.ts
cwang25 Feb 6, 2024
d077e35
address more comments
Feb 6, 2024
23cb021
address comment to let new test to test on buyCollateral attacks spec…
Feb 6, 2024
dd10703
evil token update
Feb 6, 2024
763f806
remove accidently checked in test file
Feb 7, 2024
68204da
fix last two comments
Feb 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions contracts/Comet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@
}

/**
* @dev Non-reentrant modifier
* @dev Prevents marked functions from being reentered
*/
modifier nonReentrant() {
kevincheng96 marked this conversation as resolved.
Show resolved Hide resolved
kevincheng96 marked this conversation as resolved.
Show resolved Hide resolved
nonReentrantBefore();
Expand All @@ -211,28 +211,28 @@
}

/**
* @dev This will set the flag before the call and revert if it is already set
* @dev Checks that the reentrancy flag is not set and then sets the flag
*/
function nonReentrantBefore() internal {
bytes32 slot = REENTRANCY_GUARD_FLAG_SLOT;
uint256 status;
assembly ("memory-safe") {

Check warning on line 219 in contracts/Comet.sol

View workflow job for this annotation

GitHub Actions / Contract linter

Avoid to use inline assembly. It is acceptable only in rare cases
status := sload(slot)
}

if (status == REENTRANCY_GUARD_ENTERED) revert ReentrantCallBlocked();
assembly ("memory-safe") {

Check warning on line 224 in contracts/Comet.sol

View workflow job for this annotation

GitHub Actions / Contract linter

Avoid to use inline assembly. It is acceptable only in rare cases
sstore(slot, REENTRANCY_GUARD_ENTERED)
}
}

/**
* @dev This will unset the flag after the call
* @dev Unsets the reentrancy flag
*/
function nonReentrantAfter() internal {
bytes32 slot = REENTRANCY_GUARD_FLAG_SLOT;
uint256 status;

Check warning on line 234 in contracts/Comet.sol

View workflow job for this annotation

GitHub Actions / Contract linter

Variable "status" is unused
assembly ("memory-safe") {

Check warning on line 235 in contracts/Comet.sol

View workflow job for this annotation

GitHub Actions / Contract linter

Avoid to use inline assembly. It is acceptable only in rare cases
sstore(slot, REENTRANCY_GUARD_NOT_ENTERED)
}
}
Expand Down Expand Up @@ -260,7 +260,7 @@
function getPackedAssetInternal(AssetConfig[] memory assetConfigs, uint i) internal view returns (uint256, uint256) {
AssetConfig memory assetConfig;
if (i < assetConfigs.length) {
assembly {

Check warning on line 263 in contracts/Comet.sol

View workflow job for this annotation

GitHub Actions / Contract linter

Avoid to use inline assembly. It is acceptable only in rare cases
assetConfig := mload(add(add(assetConfigs, 0x20), mul(i, 0x20)))
}
} else {
Expand Down Expand Up @@ -803,7 +803,7 @@
uint256 preTransferBalance = IERC20NonStandard(asset).balanceOf(address(this));
IERC20NonStandard(asset).transferFrom(from, address(this), amount);
bool success;
assembly ("memory-safe") {

Check warning on line 806 in contracts/Comet.sol

View workflow job for this annotation

GitHub Actions / Contract linter

Avoid to use inline assembly. It is acceptable only in rare cases
switch returndatasize()
case 0 { // This is a non-standard ERC-20
success := not(0) // set success to true
Expand All @@ -827,7 +827,7 @@
function doTransferOut(address asset, address to, uint amount) internal {
IERC20NonStandard(asset).transfer(to, amount);
bool success;
assembly ("memory-safe") {

Check warning on line 830 in contracts/Comet.sol

View workflow job for this annotation

GitHub Actions / Contract linter

Avoid to use inline assembly. It is acceptable only in rare cases
switch returndatasize()
case 0 { // This is a non-standard ERC-20
success := not(0) // set success to true
Expand Down Expand Up @@ -1380,9 +1380,9 @@
/**
* @notice Fallback to calling the extension delegate for everything else
*/
fallback() external payable {

Check warning on line 1383 in contracts/Comet.sol

View workflow job for this annotation

GitHub Actions / Contract linter

Fallback function must be simple
address delegate = extensionDelegate;
assembly {

Check warning on line 1385 in contracts/Comet.sol

View workflow job for this annotation

GitHub Actions / Contract linter

Avoid to use inline assembly. It is acceptable only in rare cases
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), delegate, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
Expand Down
14 changes: 12 additions & 2 deletions contracts/test/EvilToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ contract EvilToken is FaucetToken {
enum AttackType {
TRANSFER_FROM,
WITHDRAW_FROM,
SUPPLY_FROM
SUPPLY_FROM,
BUY_COLLATERAL
}

struct ReentryAttack {
Expand All @@ -23,6 +24,7 @@ contract EvilToken is FaucetToken {
address asset;
uint amount;
uint maxCalls;
address collateralAsset;
}

ReentryAttack public attack;
Expand All @@ -40,7 +42,8 @@ contract EvilToken is FaucetToken {
destination: address(this),
asset: address(this),
amount: 1e6,
maxCalls: type(uint).max
maxCalls: type(uint).max,
collateralAsset: address(0)
kevincheng96 marked this conversation as resolved.
Show resolved Hide resolved
});
}

Expand Down Expand Up @@ -92,6 +95,13 @@ contract EvilToken is FaucetToken {
reentryAttack.asset,
reentryAttack.amount
);
} else if (reentryAttack.attackType == AttackType.BUY_COLLATERAL) {
Comet(payable(msg.sender)).buyCollateral(
reentryAttack.collateralAsset,
0,
reentryAttack.amount,
reentryAttack.destination
);
} else {
revert("invalid reentry attack");
}
Expand Down
206 changes: 206 additions & 0 deletions deployments/goerli/usdt/migrations/1691022234_configurate_and_ens.ts
kevincheng96 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { DeploymentManager, migration } from '../../../../plugins/deployment_manager';
import { exp, getConfigurationStruct, proposal } from '../../../../src/deploy';
import { diffState, getCometConfig } from '../../../../plugins/deployment_manager/DiffState';
import { expect } from 'chai';

const COMPAddress = '0x3587b2F7E0E2D6166d6C14230e7Fe160252B0ba4';
const ENSName = 'compound-community-licenses.eth';
const ENSResolverAddress = '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001';
const ENSSubdomainLabel = 'v3-additional-grants';
const ENSSubdomain = `${ENSSubdomainLabel}.${ENSName}`;
const ENSTextRecordKey = 'v3-official-markets';
const USDCAmountToSeed = exp(5, 6);

export default migration('1691022234_configurate_and_ens', {
async prepare(deploymentManager: DeploymentManager) {
const cometFactory = await deploymentManager.deploy('cometFactory', 'CometFactory.sol', [], true);
return { newFactoryAddress: cometFactory.address };
},

enact: async (deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, { newFactoryAddress }) => {
const trace = deploymentManager.tracer();

// Import shared contracts from cUSDCv3

const AllContracts = await deploymentManager.getContracts();
const {
governor,
comet,
configurator,
cometAdmin,
rewards,
USDT,
} = AllContracts;

const configuration = await getConfigurationStruct(deploymentManager);

const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli');
const subdomainHash = ethers.utils.namehash(ENSSubdomain);
const chainId = (await deploymentManager.hre.ethers.provider.getNetwork()).chainId.toString();
const newMarketObject = { baseSymbol: 'USDT', cometAddress: comet.address };
const officialMarketsJSON = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey));
if (officialMarketsJSON[chainId]) {
officialMarketsJSON[chainId].push(newMarketObject);
} else {
officialMarketsJSON[chainId] = [newMarketObject];
}

const actions = [
// 1. Set the factory in the Configurator
{
contract: configurator,
signature: 'setFactory(address,address)',
args: [comet.address, newFactoryAddress],
},

// 2. Set the configuration in the Configurator
{
contract: configurator,
signature: 'setConfiguration(address,(address,address,address,address,address,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint104,uint104,uint104,(address,address,uint8,uint64,uint64,uint64,uint128)[]))',
args: [comet.address, configuration],
},

// 3. Deploy and upgrade to a new version of Comet
{
contract: cometAdmin,
signature: "deployAndUpgradeTo(address,address)",
args: [configurator.address, comet.address],
},

// 4. Set the rewards configuration to COMP
{
contract: rewards,
signature: "setRewardConfig(address,address)",
args: [comet.address, COMPAddress],
},

// 5. Set the official markets text record on the subdomain
{
target: ENSResolverAddress,
signature: 'setText(bytes32,string,string)',
calldata: ethers.utils.defaultAbiCoder.encode(
['bytes32', 'string', 'string'],
[subdomainHash, ENSTextRecordKey, JSON.stringify(officialMarketsJSON)]
)
},

// 6. Send USDT from Timelock to Comet
// XXX assert that funds have been transferred by diffing the balances before and after
{
contract: USDT,
signature: "transfer(address,uint256)",
args: [comet.address, exp(20_000_000, 6)],
},
];

const description = "# Initialize cUSDTv3 on Goerli"
const txn = await deploymentManager.retry(
async () => trace((await governor.propose(...await proposal(actions, description))))
);

const event = txn.events.find(event => event.event === 'ProposalCreated');
const [proposalId] = event.args;

trace(`Created proposal ${proposalId}.`);
},

async verify(deploymentManager: DeploymentManager, govDeploymentManager: DeploymentManager, preMigrationBlockNumber: number) {
const ethers = deploymentManager.hre.ethers;
await deploymentManager.spider(); // Pull in Arbitrum COMP now that reward config has been set
const {
comet,
rewards,
} = await deploymentManager.getContracts();

const config = await rewards.rewardConfig(comet.address);

// Verify state changes
// const stateChanges = await diffState(comet, getCometConfig, preMigrationBlockNumber);
// TODO: Will uncomment once the comet has been deployed
// expect(stateChanges).to.deep.equal({
// COMP: {
// supplyCap: exp(500_000, 18)
// },
// WBTC: {
// supplyCap: exp(35_000, 8)
// },
// WETH: {
// supplyCap: exp(1_000_000, 18)
// },
// baseTrackingSupplySpeed: exp(34.74 / 86400, 15, 18),
// baseTrackingBorrowSpeed: exp(34.74 / 86400, 15, 18),
// });

expect(config.token).to.be.equal(COMPAddress);
expect(config.rescaleFactor).to.be.equal(exp(1, 12));
expect(config.shouldUpscale).to.be.equal(true);

// Verify the seeded USDT reaches Comet reserve
expect(await comet.getReserves()).to.be.equal(exp(20_000_000, 6));

// Verify the official markets are updated
const ENSResolver = await deploymentManager.existing('ENSResolver', ENSResolverAddress, 'goerli');
const subdomainHash = ethers.utils.namehash(ENSSubdomain);
const officialMarkets = JSON.parse(await ENSResolver.text(subdomainHash, ENSTextRecordKey));

expect(officialMarkets).to.deep.equal({
5: [
{
baseSymbol: 'USDC',
cometAddress: '0x3EE77595A8459e93C2888b13aDB354017B198188',
},
{
baseSymbol: 'WETH',
cometAddress: '0x9A539EEc489AAA03D588212a164d0abdB5F08F5F',
},
{
baseSymbol: 'USDT',
cometAddress: comet.address,
}
],

420: [
{
baseSymbol: 'USDC',
cometAddress: '0xb8F2f9C84ceD7bBCcc1Db6FB7bb1F19A9a4adfF4'
}
],

421613: [
{
baseSymbol: 'USDC.e',
cometAddress: '0x1d573274E19174260c5aCE3f2251598959d24456',
},
{
baseSymbol: 'USDC',
cometAddress: '0x0C94d3F9D7F211630EDecAF085718Ac80821A6cA',
},
],

59140: [
{
baseSymbol: 'USDC',
cometAddress: '0xa84b24A43ba1890A165f94Ad13d0196E5fD1023a'
}
],

84531: [
{
baseSymbol: 'USDC',
cometAddress: '0xe78Fc55c884704F9485EDa042fb91BfE16fD55c1'
},
{
baseSymbol: 'WETH',
cometAddress: '0xED94f3052638620fE226a9661ead6a39C2a265bE'
}
],

80001: [
{
baseSymbol: 'USDC',
cometAddress: '0xF09F0369aB0a875254fB565E52226c88f10Bc839'
},
]
});
}
});
12 changes: 7 additions & 5 deletions test/buy-collateral-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ describe('buyCollateral', function () {
});

describe('reentrancy', function() {
it('is blocked during reentrant supply', async () => {
it('is not broken by reentrancy supply', async () => {
const wethArgs = {
initial: 1e4,
decimals: 18,
Expand Down Expand Up @@ -563,7 +563,7 @@ describe('buyCollateral', function () {
expect(evilBobPortfolio.internal.EVIL).to.equal(0);
});

it('reentrant supply is reverted', async () => {
it('reentrant buyCollateral is reverted', async () => {
const wethArgs = {
initial: 1e4,
decimals: 18,
Expand Down Expand Up @@ -596,12 +596,13 @@ describe('buyCollateral', function () {

// add attack to EVIL token
const attack = Object.assign({}, await EVIL.getAttack(), {
attackType: ReentryAttack.SupplyFrom,
attackType: ReentryAttack.BuyCollateral,
source: evilAlice.address,
destination: evilBob.address,
asset: EVIL.address,
amount: 3000e6,
maxCalls: 1
maxCalls: 1,
collateralAsset: evilWETH.address,
});
await EVIL.setAttack(attack);

Expand All @@ -615,7 +616,8 @@ describe('buyCollateral', function () {

// authorize EVIL, since callback will originate from EVIL token address
await evilComet.connect(evilAlice).allow(EVIL.address, true);
// call buyCollateral; supplyFrom is called in in callback

// call buyCollateral; supplyFrom is called in callback
await expect(evilComet
.connect(evilAlice)
.buyCollateral(
Expand Down
3 changes: 2 additions & 1 deletion test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export type Numeric = number | bigint;
export enum ReentryAttack {
TransferFrom = 0,
WithdrawFrom = 1,
SupplyFrom = 2
SupplyFrom = 2,
BuyCollateral = 3,
}

export type ProtocolOpts = {
Expand Down
Loading