Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions abis/contracts/implementations/nodes/Vehicle.sol/Vehicle.json
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,41 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "manufacturerNode",
"type": "uint256"
},
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"components": [
{
"internalType": "string",
"name": "attribute",
"type": "string"
},
{
"internalType": "string",
"name": "info",
"type": "string"
}
],
"internalType": "struct AttributeInfoPair[]",
"name": "attrInfo",
"type": "tuple[]"
}
],
"name": "mintVehicle",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
Expand Down
34 changes: 34 additions & 0 deletions contracts/implementations/nodes/Vehicle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,40 @@ contract Vehicle is
ChargingInternal._chargeDcx(msg.sender, MINT_VEHICLE_OPERATION);
}

/**
* @notice Function to mint a vehicle without a Device Definition Id
* @dev Device Definitions are tracked off-chain; on-chain mints no longer carry them
* @param manufacturerNode Parent manufacturer node id
* @param owner The address of the new owner
* @param attrInfo List of attribute-info pairs to be added
*/
function mintVehicle(
uint256 manufacturerNode,
address owner,
AttributeInfoPair[] calldata attrInfo
) external {
VehicleStorage.Storage storage vs = VehicleStorage.getStorage();
address vehicleIdProxyAddress = vs.idProxyAddress;

if (
!INFT(ManufacturerStorage.getStorage().idProxyAddress).exists(
manufacturerNode
)
) revert InvalidParentNode(manufacturerNode);

uint256 newTokenId = INFT(vehicleIdProxyAddress).safeMint(owner);

NodesStorage
.getStorage()
.nodes[vehicleIdProxyAddress][newTokenId].parentNode = manufacturerNode;

emit VehicleNodeMinted(manufacturerNode, newTokenId, owner);

if (attrInfo.length > 0) _setInfos(newTokenId, attrInfo);

ChargingInternal._chargeDcx(msg.sender, MINT_VEHICLE_OPERATION);
}

/**
* @notice Function to mint a vehicle with a Device Definition Id and associate it with a Storage Node Id
* @dev This function creates a new vehicle node, associates it with a manufacturer,
Expand Down
173 changes: 173 additions & 0 deletions test/nodes/Vehicle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,179 @@ describe('Vehicle', function () {
});
});

describe('mintVehicle(uint256,address,(string,string)[])', () => {
context('Error handling', () => {
it('Should revert if parent node is not a manufacturer node', async () => {
await expect(
vehicleInstance
.connect(admin)
.mintVehicle(
99,
user1.address,
C.mockVehicleAttributeInfoPairs,
)
).to.be.revertedWithCustomError(vehicleInstance, 'InvalidParentNode')
.withArgs(99);
});
it('Should revert if attribute is not whitelisted', async () => {
await expect(
vehicleInstance
.connect(admin)
.mintVehicle(
1,
user1.address,
C.mockVehicleAttributeInfoPairsNotWhitelisted
)
).to.be.revertedWithCustomError(
vehicleInstance,
'AttributeNotWhitelisted'
).withArgs(C.mockVehicleAttributeInfoPairsNotWhitelisted[1].attribute);
});
});

context('State', () => {
it('Should correctly set parent node', async () => {
await vehicleInstance
.connect(admin)
.mintVehicle(
1,
user1.address,
C.mockVehicleAttributeInfoPairs
);

const parentNode = await nodesInstance.getParentNode(
await vehicleIdInstance.getAddress(),
1
);
expect(parentNode).to.be.equal(1);
});
it('Should correctly set node owner', async () => {
await vehicleInstance
.connect(admin)
.mintVehicle(
1,
user1.address,
C.mockVehicleAttributeInfoPairs
);

expect(await vehicleIdInstance.ownerOf(1)).to.be.equal(user1.address);
});
it('Should not set a Device Definition Id', async () => {
await vehicleInstance
.connect(admin)
.mintVehicle(
1,
user1.address,
C.mockVehicleAttributeInfoPairs
);

expect(
await vehicleInstance.getDeviceDefinitionIdByVehicleId(1)
).to.be.equal('');
});
it('Should correctly set infos', async () => {
await vehicleInstance
.connect(admin)
.mintVehicle(
1,
user1.address,
C.mockVehicleAttributeInfoPairs
);

expect(
await nodesInstance.getInfo(
await vehicleIdInstance.getAddress(),
1,
C.mockVehicleAttribute1
)
).to.be.equal(C.mockVehicleInfo1);
expect(
await nodesInstance.getInfo(
await vehicleIdInstance.getAddress(),
1,
C.mockVehicleAttribute2
)
).to.be.equal(C.mockVehicleInfo2);
});
it('Should correctly burn DIMO Credit tokens from the sender', async () => {
await expect(() =>
vehicleInstance
.connect(admin)
.mintVehicle(
1,
user1.address,
C.mockVehicleAttributeInfoPairs
)
).changeTokenBalance(
mockDimoCreditInstance,
admin.address,
-C.MINT_VEHICLE_OPERATION_COST
);
});
});

context('Events', () => {
it('Should emit VehicleNodeMinted event with correct params', async () => {
await expect(
vehicleInstance
.connect(admin)
.mintVehicle(
1,
user1.address,
C.mockVehicleAttributeInfoPairs
)
)
.to.emit(vehicleInstance, 'VehicleNodeMinted')
.withArgs(1, 1, user1.address);
});
it('Should not emit VehicleNodeMintedWithDeviceDefinition event', async () => {
await expect(
vehicleInstance
.connect(admin)
.mintVehicle(
1,
user1.address,
C.mockVehicleAttributeInfoPairs
)
).to.not.emit(vehicleInstance, 'VehicleNodeMintedWithDeviceDefinition');
});
it('Should emit VehicleAttributeSet events with correct params', async () => {
await expect(
vehicleInstance
.connect(admin)
.mintVehicle(
1,
user1.address,
C.mockVehicleAttributeInfoPairs
)
)
.to.emit(vehicleInstance, 'VehicleAttributeSet')
.withArgs(
1,
C.mockVehicleAttributeInfoPairs[0].attribute,
C.mockVehicleAttributeInfoPairs[0].info
)
.to.emit(vehicleInstance, 'VehicleAttributeSet')
.withArgs(
1,
C.mockVehicleAttributeInfoPairs[1].attribute,
C.mockVehicleAttributeInfoPairs[1].info
);
});
it('Should not emit VehicleAttributeSet event if attrInfo is empty', async () => {
await expect(
vehicleInstance
.connect(admin)
.mintVehicle(
1,
user1.address,
[]
)
).to.not.emit(vehicleInstance, 'VehicleAttributeSet');
});
});
});

describe('mintVehicleWithDeviceDefinition(uint256,address,uint256,string,(string,string)[])', () => {
context('Error handling', () => {
it('Should revert if parent node is not a manufacturer node', async () => {
Expand Down
Loading