Skip to content
This repository has been archived by the owner on Jan 15, 2024. It is now read-only.

Commit

Permalink
Contract: check domestic asset code for mint notes
Browse files Browse the repository at this point in the history
- Rename check asset -> check foreign asset to make room for check
  domestic asset code function.
- Only pass required data to check asset code functions
- Set msg.sender in asset code tests
- Add unit tests for domestic asset code check
- Integration test for check domestic asset code

Close #390
Close #353
  • Loading branch information
sveitser committed Jan 26, 2022
1 parent 9561bcd commit 0c498d0
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 69 deletions.
35 changes: 29 additions & 6 deletions contracts/contracts/CAPE.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ contract CAPE is RecordsMerkleTree, RootStore, AssetRegistry {

bytes public constant CAPE_BURN_MAGIC_BYTES = "TRICAPE burn";
bytes13 public constant DOM_SEP_FOREIGN_ASSET = "FOREIGN_ASSET";
bytes14 public constant DOM_SEP_DOMESTIC_ASSET = "DOMESTIC_ASSET";

event BlockCommitted(uint64 indexed height, bool[] includedNotes);

Expand Down Expand Up @@ -183,17 +184,39 @@ contract CAPE is RecordsMerkleTree, RootStore, AssetRegistry {
/// @param erc20Address address of the ERC20 token corresponding to the deposit.
function depositErc20(RecordOpening memory ro, address erc20Address) public {
address depositorAddress = msg.sender;
_checkAssetCode(ro, erc20Address);
_checkForeignAssetCode(ro.assetDef.code, erc20Address);
}

/// @notice Checks if the asset definition code is correctly derived from the ERC20 address
/// of the token and the address of the depositor.
/// @dev requires "view" to access msg.sender
function _checkAssetCode(RecordOpening memory ro, address erc20Address) internal view {
function _checkForeignAssetCode(uint256 assetDefinitionCode, address erc20Address)
internal
view
{
bytes memory description = _computeAssetDescription(erc20Address, msg.sender);
bytes memory randomBytes = abi.encodePacked(
bytes memory randomBytes = bytes.concat(
keccak256(bytes.concat(DOM_SEP_FOREIGN_ASSET, description))
);
uint256 code = BN254.fromLeBytesModOrder(randomBytes);
require(code == ro.assetDef.code, "Wrong asset code");
uint256 derivedCode = BN254.fromLeBytesModOrder(randomBytes);
require(derivedCode == assetDefinitionCode, "Wrong foreign asset code");
}

/// @notice Checks if the asset definition code is correctly derived from the internal asset code.
function _checkDomesticAssetCode(uint256 assetDefinitionCode, uint256 internalAssetCode)
internal
view
{
bytes memory randomBytes = bytes.concat(
keccak256(
bytes.concat(
DOM_SEP_DOMESTIC_ASSET,
bytes32(Transcript.reverseEndianness(internalAssetCode))
)
)
);
uint256 derivedCode = BN254.fromLeBytesModOrder(randomBytes);
require(derivedCode == assetDefinitionCode, "Wrong domestic asset code");
}

// TODO consider inlining once asset description is finalized. Until then it's useful
Expand Down Expand Up @@ -244,7 +267,7 @@ contract CAPE is RecordsMerkleTree, RootStore, AssetRegistry {
comms.add(note.mintComm);
comms.add(note.chgComm);
includedNotes[i] = true;
// TODO check domestic (aap-native) asset code during verification of a mint note. See https://github.com/SpectrumXYZ/jellyfish-apps/blob/main/aap/src/mint.rs#L157-L159
_checkDomesticAssetCode(note.mintAssetDef.code, note.mintInternalAssetCode);
// TODO extract proof for batch verification
}

Expand Down
11 changes: 9 additions & 2 deletions contracts/contracts/mocks/TestCAPE.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,15 @@ contract TestCAPE is CAPE {
return _computeMaxCommitments(newBlock);
}

function checkAssetCode(RecordOpening memory ro, address erc20Address) public view {
_checkAssetCode(ro, erc20Address);
function checkForeignAssetCode(uint256 assetDefinitionCode, address erc20Address) public view {
_checkForeignAssetCode(assetDefinitionCode, erc20Address);
}

function checkDomesticAssetCode(uint256 assetDefinitionCode, uint256 internalAssetCode)
public
view
{
_checkDomesticAssetCode(assetDefinitionCode, internalAssetCode);
}

function computeAssetDescription(address erc20Address, address sponsor)
Expand Down
2 changes: 1 addition & 1 deletion contracts/contracts/mocks/TestCapeTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ contract TestCapeTypes {
return root;
}

function checkAssetCode(uint256 code) public pure returns (uint256) {
function checkForeignAssetCode(uint256 code) public pure returns (uint256) {
return code;
}

Expand Down
184 changes: 124 additions & 60 deletions contracts/rust/src/cape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,15 +268,14 @@ mod tests {
use ethers::prelude::{
k256::ecdsa::SigningKey, Http, Provider, SignerMiddleware, Wallet, U256,
};
use jf_aap::structs::{
AssetCode, AssetCodeSeed, AssetDefinition, AssetPolicy, FreezeFlag, RecordOpening,
};
use jf_aap::structs::{AssetCode, AssetCodeSeed, InternalAssetCode, RecordOpening};
use rand::Rng;

use crate::assertion::Matcher;
use crate::ethereum::{deploy, get_funded_deployer};
use crate::types::{
GenericInto, MerkleRootSol, NullifierSol, RecordCommitmentSol, TestCAPE, TestCapeTypes,
AssetCodeSol, GenericInto, InternalAssetCodeSol, MerkleRootSol, NullifierSol,
RecordCommitmentSol, TestCAPE, TestCapeTypes,
};
use anyhow::Result;
use jf_aap::keys::UserPubKey;
Expand Down Expand Up @@ -764,104 +763,169 @@ mod tests {
}

#[tokio::test]
async fn test_check_asset_code() -> Result<()> {
async fn test_check_foreign_asset_code() -> Result<()> {
let contract = deploy_cape_test().await;

// Fails for random record opening with random asset code.
let rng = &mut ark_std::test_rng();
let ro = RecordOpening::rand_for_test(rng);
contract
.check_asset_code(ro.generic_into::<sol::RecordOpening>(), Address::random())
.check_foreign_asset_code(
ro.asset_def.code.generic_into::<sol::AssetCodeSol>().0,
Address::random(),
)
.call()
.await
.should_revert_with_message("Wrong asset code");
.should_revert_with_message("Wrong foreign asset code");

// Fails for record opening with domestic asset code.
let erc20_address = Address::random();
let domestic_asset_def = AssetDefinition::new(
AssetCode::new_domestic(AssetCodeSeed::generate(rng), erc20_address.as_bytes()),
AssetPolicy::rand_for_test(rng),
)
.unwrap();
// This is the first account from the test mnemonic
// TODO define elsewhere to make it usable from other tests
let sponsor = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse::<Address>()?;
let erc20_code = Erc20Code(EthereumAddr(erc20_address.to_fixed_bytes()));

// Fails for domestic asset code.
let domestic_asset_code =
AssetCode::new_domestic(AssetCodeSeed::generate(rng), erc20_address.as_bytes());
contract
.check_asset_code(
RecordOpening::new(
rng,
1u64,
domestic_asset_def,
UserPubKey::default(),
FreezeFlag::Unfrozen,
)
.generic_into::<sol::RecordOpening>(),
.check_foreign_asset_code(
domestic_asset_code.generic_into::<AssetCodeSol>().0,
erc20_address,
)
.from(sponsor)
.call()
.await
.should_revert_with_message("Wrong asset code");

// TODO This is the first address, we should derive it from TEST_MNEMONIC
// msg.sender is the first address derived from TEST_MNEMONIC in hardhat
// let sponsor = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse::<Address>()?;
// but Address(0) in geth! why?
let sponsor = Address::zero();
let erc20_code = Erc20Code(EthereumAddr(erc20_address.to_fixed_bytes()));
.should_revert_with_message("Wrong foreign asset code");

// Fails if txn sender address does not match sponsor in asset code.
let description_wrong_sponsor = erc20_asset_description(
&erc20_code,
&EthereumAddr(Address::random().to_fixed_bytes()),
);
let asset_def_wrong_sponsor = AssetDefinition::new(
AssetCode::new_foreign(&description_wrong_sponsor),
AssetPolicy::rand_for_test(rng),
)
.unwrap();
let ro_wrong_sponsor = RecordOpening::new(
rng,
1u64,
asset_def_wrong_sponsor,
UserPubKey::default(),
FreezeFlag::Unfrozen,
);
let asset_code_wrong_sponsor = AssetCode::new_foreign(&description_wrong_sponsor);
contract
.check_asset_code(
ro_wrong_sponsor.generic_into::<sol::RecordOpening>(),
.check_foreign_asset_code(
asset_code_wrong_sponsor.generic_into::<AssetCodeSol>().0,
sponsor,
)
.from(sponsor)
.call()
.await
.should_revert_with_message("Wrong asset code");
.should_revert_with_message("Wrong foreign asset code");

// Create a correct record opening with foreign asset code.
let description =
erc20_asset_description(&erc20_code, &EthereumAddr(sponsor.to_fixed_bytes()));
let asset_code = AssetCode::new_foreign(&description);
let asset_def = AssetDefinition::new(asset_code, AssetPolicy::rand_for_test(rng)).unwrap();
let ro = RecordOpening::new(
rng,
1u64,
asset_def,
UserPubKey::default(),
FreezeFlag::Unfrozen,
);

// Fails for record opening with random erc20 address.
// Fails for random erc20 address.
contract
.check_asset_code(
ro.clone().generic_into::<sol::RecordOpening>(),
.check_foreign_asset_code(
asset_code.generic_into::<sol::AssetCodeSol>().0,
Address::random(),
)
.from(sponsor)
.call()
.await
.should_revert_with_message("Wrong foreign asset code");

// Passes for correctly derived asset code
contract
.check_foreign_asset_code(
asset_code.generic_into::<sol::AssetCodeSol>().0,
erc20_address,
)
.from(sponsor)
.call()
.await
.should_revert_with_message("Wrong asset code");
.should_not_revert();

Ok(())
}

// Passes for correct record opening and erc20 address.
#[tokio::test]
async fn test_check_domestic_asset_code() -> Result<()> {
let contract = deploy_cape_test().await;

// Create a matching pair of codes
let rng = &mut ark_std::test_rng();
let description = b"aap_usdx";
let seed = AssetCodeSeed::generate(rng);
let internal_asset_code = InternalAssetCode::new(seed, description);
let asset_code = AssetCode::new_domestic(seed, description);

// Passes for matching asset codes
contract
.check_asset_code(ro.generic_into::<sol::RecordOpening>(), erc20_address)
.check_domestic_asset_code(
asset_code.generic_into::<AssetCodeSol>().0,
internal_asset_code.generic_into::<InternalAssetCodeSol>().0,
)
.call()
.await
.should_not_revert();

// Fails with non-matching description
contract
.check_domestic_asset_code(
AssetCode::new_domestic(seed, b"other description")
.generic_into::<AssetCodeSol>()
.0,
internal_asset_code.generic_into::<InternalAssetCodeSol>().0,
)
.call()
.await
.should_revert_with_message("Wrong domestic asset code");

// Fails for foreign asset code
contract
.check_domestic_asset_code(
AssetCode::new_foreign(description)
.generic_into::<AssetCodeSol>()
.0,
internal_asset_code.generic_into::<InternalAssetCodeSol>().0,
)
.call()
.await
.should_revert_with_message("Wrong domestic asset code");

// Fails if internal asset code doesn't match (different seed)
contract
.check_domestic_asset_code(
asset_code.generic_into::<AssetCodeSol>().0,
InternalAssetCode::new(AssetCodeSeed::generate(rng), description)
.generic_into::<InternalAssetCodeSol>()
.0,
)
.call()
.await
.should_revert_with_message("Wrong domestic asset code");

Ok(())
}

#[tokio::test]
async fn test_check_domestic_asset_code_in_submit_cape_block() -> Result<()> {
let contract = deploy_cape_test().await;
let rng = &mut ark_std::test_rng();
let params = TxnsParams::generate_txns(rng, 0, 1, 0);

contract
.add_root(params.merkle_root.generic_into::<MerkleRootSol>().0)
.send()
.await?
.await?;

let mut block = CapeBlock::generate(params.txns, vec![], UserPubKey::default().address())?;

// Set a wrong internal asset code on the mint note
block.mint_notes[0].mint_internal_asset_code =
InternalAssetCode::new(AssetCodeSeed::generate(rng), b"description");

contract
.submit_cape_block(block.into(), vec![])
.call()
.await
.should_revert_with_message("Wrong domestic asset code");

Ok(())
}

Expand Down

0 comments on commit 0c498d0

Please sign in to comment.